悲觀鎖與樂觀鎖的實現(詳情圖解)

一、前言

  • 在了解悲觀鎖和樂觀鎖之前,我們先了解一下什麼是鎖,為什麼要用到鎖?

  • 技術來源於生活,鎖不僅在程序中存在,在現實中我們也隨處可見,例如我們上下班打卡的指紋鎖,保險柜上的密碼鎖,以及我們我們登錄的用戶名和密碼也是一種鎖,生活中用到鎖可以保護我們人身安全(指紋鎖)、財產安全(保險櫃密碼鎖)、信息安全(用戶名密碼鎖),讓我們更放心的去使用和生活,因為有鎖,我們不用去擔心個人的財產和信息泄露。

  • 而程序中的鎖,則是用來保證我們數據安全的機制和手段,例如當我們有多個線程去訪問修改共享變量的時候,我們可以給修改操作加鎖(syncronized)。當多個用戶修改表中同一數據時,我們可以給該行數據上鎖(行鎖)。因此,當程序中可能出現併發的情況時,我們就需要通過一定的手段來保證在併發情況下數據的準確性,通過這種手段保證了當前用戶和其他用戶一起操作時,所得到的結果和他單獨操作時的結果是一樣的

  • 沒有做好併發控制,就可能導致臟讀、幻讀和不可重複讀等問題,如下圖所示:

    由於併發操作,如果沒有加鎖進行併發控制,數據庫的最終的一條數據可能為3也有可能為5,導致數值不準確

二、悲觀鎖和樂觀鎖

首先我們需要清楚的一點就是無論是悲觀鎖還是樂觀鎖,都是人們定義出來的概念,可以認為是一種思想。

2.1、悲觀鎖

悲觀鎖(Pessimistic Lock): 就是很悲觀,每次去拿數據的時候都認為別人會修改。所以每次在拿數據的時候都會上鎖。這樣別人想拿數據就被擋住,直到悲觀鎖被釋放,悲觀鎖中的共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程

但是在效率方面,處理加鎖的機制會產生額外的開銷,還有增加產生死鎖的機會。另外還會降低并行性,如果已經鎖定了一個線程A,其他線程就必須等待該線程A處理完才可以處理

數據庫中的行鎖,表鎖,讀鎖(共享鎖),寫鎖(排他鎖),以及syncronized實現的鎖均為悲觀鎖

悲觀併發控制實際上是“先取鎖再訪問”的保守策略,為數據處理的安全提供了保證,

2.2、樂觀鎖

樂觀鎖(Optimistic Lock): 就是很樂觀,每次去拿數據的時候都認為別人不會修改。所以不會上鎖,但是如果想要更新數據,則會在更新前檢查在讀取至更新這段時間別人有沒有修改過這個數據。如果修改過,則重新讀取,再次嘗試更新,循環上述步驟直到更新成功(當然也允許更新失敗的線程放棄操作),樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量

相對於悲觀鎖,在對數據庫進行處理的時候,樂觀鎖並不會使用數據庫提供的鎖機制。一般的實現樂觀鎖的方式就是記錄數據版本(version)或者是時間戳來實現,不過使用版本記錄是最常用的。

樂觀控制相信事務之間的數據競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。

三、鎖的實現

悲觀鎖阻塞事務、樂觀鎖回滾重試:它們各有優缺點,不要認為一種一定好於另一種。像樂觀鎖適用於寫比較少的情況下,即衝突真的很少發生的時候,這樣可以省去鎖的開銷,加大了系統的整個吞吐量。但如果經常產生衝突,上層應用會不斷的進行重試,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。

3.1 悲觀鎖的實現方式

場景:

有用戶A和用戶B,在同一家店鋪去購買同一個商品,但是商品的可購買數量只有一個

下面是這個店鋪的商品表t_goods結構和表中的數據:

在不加鎖的情況下,如果用戶A和用戶B同時下單,就會報錯。

悲觀鎖的實現,往往依靠數據庫提供的鎖機制,在數據庫中,我們如何用悲觀鎖去解決這個事情呢?

  1. 加入當用戶A對下單購買商品(臭豆腐)的時候,先去嘗試對該數據(臭豆腐)加上悲觀鎖
  2. 加鎖失敗:說明商品(臭豆腐)正在被其他事務進行修改,當前查詢需要等待或者拋出異常,具體返回的方式需要由開發者根據具體情況去定義
  3. 加鎖成功:對商品(臭豆腐)進行修改,也就是只有用戶A能買,用戶B想買(臭豆腐)就必須一直等待。當用戶A買好后,用戶B再想去買(臭豆腐)的時候會發現數量已經為0,那麼B看到后就會放棄購買
  4. 在此期間如果有其他對該數據(臭豆腐)做修改或加鎖的操作,都會等待我們解鎖后或者直接拋出異常

那麼如何加上悲觀鎖呢?我們可以通過以下語句給id=2的這行數據加上悲觀鎖,首先關閉MySQL數據庫的自動提交屬性。因為MySQL默認使用autocommit模式,也就是說,當我們執行一個更新操作后,MySQL會立刻將結果進行提交,(sql語句:set autocommit=0)

悲觀鎖加鎖sql語句: select num from t_goods where id = 2 for update

我們通過開啟mysql的兩個會話,也就是兩個命令行來演示:

事務A:
我們可以看到數據是立刻馬上就可以查詢出來,num=1

事務B:
我們是可以看到,事務B會一直等待事務A釋放鎖。如果事務A長期不釋放鎖,那麼最終事務B將會報錯,報錯如下:Lock wait timeout exceeded; try restarting transaction,表示語句已被鎖住

現在我們讓事務A執行命令去修改數據,讓臭豆腐的數量減一,然後查看修改后的數據,最後commit,結束事務

我們可以看到當我們事務A執行完成之後,臭豆腐的庫存只有0個了,這個時候我們用戶B再來購買這個臭豆腐的時候就會發現,最後一個臭豆腐已經被用戶A購買完了,那麼用戶B只能放棄購買臭豆腐了。

通過悲觀鎖我們可以解決因為商品庫存不足,導致的商品超出庫存的售賣。

3.1 樂觀鎖的實現方式

對於上面的應用場景,我們應該怎麼用樂觀鎖去解決呢?在上面的樂觀鎖中,我們有提到使用版本號(version)來解決,所以我們需要在t_goods加上版本號,調整后的sql表結構如下:

具體操作步驟如下:
1、首先用戶A和用戶B同時將臭豆腐(id=2)的數據查出來
2、然後用戶A先買,用戶A將(id=1和version=0)作為條件進行數據更新,將數量-1,並且將版本號+1。此時版本號變為1。用戶A此時就完成了商品的購買
3、 用戶B開始買,用戶B也將(id=1和version=0)作為條件進行數據更新
4、更新完后,發現更新的數據行數為0,此時就說明已經有人改動過數據,此時就應該提示用戶B重新查看最新數據購買

1、首先我們開啟兩個會話窗口,輸入查詢語句:select num from t_goods where id = 2
事務A:

事務B:

這個時候事務A和事務B同時獲取相同的數據

2、此時事務A進行更新數據的操作,然後在查詢更新后的數據

這個時候我們可以看到事務A更新成功,並且庫存-1 版本號+1成功

2、此時事務B進行更新數據的操作,然後在查詢更新后的數據

可以看到最終修改的時候失敗,數據沒有改變。此時就需要我們告知用戶B重新處理

3.1.1 CAS

說到樂觀鎖,就必須提到一個概念:CAS
什麼是CAS呢?Compare-and-Swap,即比較並替換,也有叫做Compare-and-Set的,比較並設置。
1、比較:讀取到了一個值A,在將其更新為B之前,檢查原值是否仍為A(未被其他線程改動)。
2、設置:如果是,將A更新為B,結束。[1]如果不是,則什麼都不做。
上面的兩步操作是原子性的,可以簡單地理解為瞬間完成,在CPU看來就是一步操作。
有了CAS,就可以實現一個樂觀鎖,允許多個線程同時讀取(因為根本沒有加鎖操作),但是只有一個線程可以成功更新數據,並導致其他要更新數據的線程回滾重試。 CAS利用CPU指令,從硬件層面保證了操作的原子性,以達到類似於鎖的效果。

Java中真正的CAS操作調用的native方法
因為整個過程中並沒有“加鎖”和“解鎖”操作,因此樂觀鎖策略也被稱為無鎖編程。換句話說,樂觀鎖其實不是“鎖”,它僅僅是一個循環重試CAS的算法而已,但是CAS有一個問題那就是會產生ABA問題,什麼是ABA問題,以及如何解決呢?

ABA 問題:
如果一個變量V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的,因為在這段時間它的值可能被改為其他值,然後又改回A,那CAS操作就會誤認為它從來沒有被修改過。這個問題被稱為CAS操作的 “ABA”問題。

ABA 問題解決:
我們需要加上一個版本號(Version),在每次提交的時候將版本號+1操作,那麼下個線程去提交修改的時候,會帶上版本號去判斷,如果版本修改了,那麼線程重試或者提示錯誤信息~

四、如何選擇

悲觀鎖阻塞事務,樂觀鎖回滾重試,它們各有優缺點,不要認為一種一定好於另一種。像樂觀鎖適用於寫比較少的情況下,即衝突真的很少發生的時候,這樣可以省去鎖的開銷,加大了系統的整個吞吐量。

但如果經常產生衝突,上層應用會不斷的進行重試,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。

注意點:

1、樂觀鎖並未真正加鎖,所以效率高。一旦鎖的粒度掌握不好,更新失敗的概率就會比較高,容易發生業務失敗。

2、悲觀鎖依賴數據庫鎖,效率低。更新失敗的概率比較低。

五、總結

這篇文章講解了悲觀鎖與樂觀鎖的區別,以及實現場景,不管是悲觀鎖還是樂觀鎖都是人們定義出來的概念,是一種思想,如何有有疑問或者問題的小夥伴可以在下面進行留言,小農看到了會第一時間回復大家,謝謝,大家加油~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

您可能也會喜歡…