悲觀鎖和樂觀鎖總結(jié)

概念

樂觀鎖也叫共享鎖、讀鎖,事務(wù)T給對象A加了樂觀鎖,則其他事務(wù)還可以給對象A加樂觀鎖。但不能加被排他鎖。也就是可以同時(shí)讀數(shù)據(jù),但讀的過程中,不允許任何寫。

悲觀鎖也叫排他鎖、寫鎖,事務(wù)T給對象A加了悲觀鎖,則其他事務(wù)不可以給對象A加任何鎖。也就是在寫期間,不允許任何讀、寫操作。

實(shí)現(xiàn)

在數(shù)據(jù)庫用select for update屬于樂觀鎖,update/delete屬于悲觀鎖。

在java中,同步塊屬于悲觀鎖。ReentrantReadWriteLock有ReadLock/WriteLock,分別實(shí)現(xiàn)樂觀鎖和悲觀鎖。注意:java的鎖只對單個(gè)jvm有效。所以在集群環(huán)境下,必須借助數(shù)據(jù)庫或者第三方組件(例如:zookeeper,redis)。

應(yīng)用場景

減庫存:庫存為10,每一個(gè)訂單庫存減1。
分析:減庫存分兩個(gè)步驟:1.判斷是否有庫存(庫存為0);2.如果有則減1,如果沒有則返回。
并發(fā)環(huán)境下,存在兩個(gè)問題:

  1. A/B兩個(gè)用戶同時(shí)判斷庫存都只剩一件,進(jìn)行減1操作,產(chǎn)生超發(fā)。
  2. 同時(shí)進(jìn)行對庫存做減1操作,庫存少減了1(例如庫存10。兩個(gè)進(jìn)程同時(shí)來減1,A進(jìn)程做stock=10-1,B進(jìn)程也做stock=10-1,則庫存變成了9,而不是期望的8)。
    假設(shè)庫存是一個(gè)變量stock 。

** 方案1(悲觀鎖)**:判斷stock 是否大于0前,先將變量加一把寫鎖,然后再判斷。這樣其他進(jìn)程連判斷語句都進(jìn)不了。

private final ReadWriteLock lock = new ReentrantReadWriteLock();   
private final Lock w = lock.writeLock();  
    
w.lock();
if(stock  >0) stock --; 
else return false;

方案2(樂觀鎖):判斷A是否大于0前,先將變量加一把讀鎖,判斷成功準(zhǔn)備寫之前,將讀鎖升級為寫鎖。

private final ReadWriteLock lock = new ReentrantReadWriteLock();  
private final Lock r = lock.readLock();  

r.lock();
if(a>0) {
  w.lock(); 
  a--; 
}
else return false;

在集群環(huán)境下的方案:借助數(shù)據(jù)庫的悲觀鎖和樂觀鎖。由于數(shù)據(jù)庫的鎖資源非常寶貴,為了增加系統(tǒng)的吞吐量,一般采用版本號(hào)的方案。

假設(shè)數(shù)據(jù)庫表Stock,有兩個(gè)字段num,version,初始<10,0>。

select * from Stock;  //這里并沒有加鎖
if(Stock.num>0) {
  ver = Stock.version;
  update Stock set num=num-1,version=version+1 where version = ver;
  if (影響的行數(shù) == 1) {
     return true;
  } else {
    return false;
  }
} else {
   return false;
}

另外,可以將上述方案結(jié)合起來。在java端做一個(gè)庫存是否為空的變量(靜態(tài)),這樣就避免在庫存已經(jīng)為0的情況下還去查數(shù)據(jù)庫。

private final ReadWriteLock lock = new ReentrantReadWriteLock();   
private final Lock w = lock.writeLock();  
static boolean isZero = false;

r.lock();
if(!isZero) {
select * from Stock;  //這里并沒有加鎖
if(Stock.num>0) {
  ver = Stock.version;
  update Stock set num=num-1,version=version+1 where version = ver;
  if (影響的行數(shù) == 1) {
    //判斷是否還有庫存并修改變量
    select count(1) cnt from Stock  where num = 0;
    w.lock();
    isZero  = true;
    w.unlock();
     return true;
  } else {
    return false;
  }
} else {
   return false;
}

這里簡化了一個(gè)問題,庫存是針對某件商品的,所以應(yīng)該是一個(gè)Map的鎖。如果直接針對Map加鎖勢必導(dǎo)致多個(gè)商品同時(shí)被加鎖。可以使用:

  1. java.util.concurrent.ConcurrentHashMap 類;
  2. 利用ReentrantReadWriteLock實(shí)現(xiàn)一個(gè)ReadWriteMap。見網(wǎng)絡(luò)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容