程序員進階樂觀鎖與悲觀鎖

程序員進階樂觀鎖與悲觀鎖

併發鎖

  生產環境中,併發場景是十分常見的,多個線程的併發自然會難以避免的訪問到共享資源。而一旦出現多個線程共享同一個資源的時候,就會出現數據混亂的問題。最常見的就是先判斷後操作的場景,舉個例子,小明和小黃吃壞了肚子,想要上廁所,結果他倆同時看到一個空的位置,於是都認為沒人自己可以上廁所,結果兩個人都擠在了廁所里。那麼該如何解決這個問題呢,沒錯,我們可以加上一把鎖,小明上廁所的時候把門關上,小黃阻塞著等待。通過將併發變為按序單一執行,從而解決數據混亂的問題。

  下面我們就介紹一下常用的樂觀鎖和悲觀鎖,以及在Java中的應用。

樂觀鎖(CAS)

  • 樂觀鎖:顧名思義總是樂觀的認為這個數據沒有在同一時間被其他人操作變更

    Advertisements

  • 實現:CAS(compare and set)

  • 優點:吞吐量高,適用於數據衝突相對較少的場景

  • 缺點:受限於外部系統,可能會引起臟讀,且在數據衝突很大的場景下,性能反而可能更低

  我們主要介紹樂觀鎖思想的實現CAS,即compare and set,先比較再設置。還是以之前小明小黃為例,即在上廁所前先做一次和之前認為的情況做一次比較(之前認為沒人),如果一致則上廁所。核心思路就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。下面我們介紹一下CAS在java中的實現

AtomicInteger & AtomicReference<T>

  AtomicInteger & AtomicReference<T>這兩個類,相比大家都很熟悉。Java中提供了這些原子類,來解決在多線程併發場景下共享數據的混亂問題,而其實現思想即為CAS。而其底層實現的native方法依賴於外部系統,利用JNI來完成CPU指令的操作。我們來分析一下代碼即可知

Advertisements

AtomicInteger & AtomicReference

unsafe

ABA問題

  當時光是這樣如此還存在著一個問題,假設線程2將值A變為B之後又將B變為A,此時線程2過來發現值還是A,單其實並不是原來的A了,卻仍將其值變為了C。我們通過代碼的方式來看一下。

ABA問題

  面對這種問題,我們可以設置一個版本號,在比對值的同時,在增加對版本號的比對。以此來解決ABA的問題,在Java提供了AtomicStampedReference類來實現帶版本號的cas操作。

帶版本號的CAS

資料庫實現樂觀鎖

  除了利用Java底層依賴CPU指令來完成CAS操作,我們還可以借用資料庫來實現。其核心思想仍然與上面的相同,實現的核心在於以下的sql

  update goods set num= newnum, version = oldversion+1 where version = oldversion

悲觀鎖

  • 悲觀鎖:認為數據在同一時刻總會被修改

  • 優點:更嚴格的避免數據混亂問題

  • 缺點:性能消耗大

  在Java中可以直接通過關鍵字syncrhoized來實現。syncrhoized是一種獨佔鎖,當一個線程獲取到該鎖之後,其他線程就會掛起,一直等待鎖釋放之後,才會繼續執行。這樣就能保證在併發的情況下數據不會混亂。但是相應的,其對性能的損耗也較大。

  在mysql中可以使用select…for update實現悲觀鎖。這樣那條數據就被我們鎖定了,其它的事務必須等本次事務提交之後才能執行。從而保證數據不會被其他事務更改從而導致數據的異常。但是select…for update不會阻塞select的查詢。

  需要注意的是mysql在採用InnoDB時,默認為行鎖,且只有明確額指定主鍵,MySQL 才會執行行鎖,鎖住對應的那條數據,否則MySQL 將會執行表鎖(將整個數據表單給鎖住)。

  

Advertisements

你可能會喜歡