樂觀鎖VS悲觀鎖

1、樂觀鎖

樂觀鎖顧名思義就是在操作時很樂觀,認爲操作不會產生併發問題(不會有其他線程對數據進行修改),因此不會上鎖。但是在更新時會判斷其他線程在這之前有沒有對數據進行修改,一般會使用版本號機制CAS(compare and swap)算法實現。
樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。

1.1 版本號機制

1、過程:

  • 取出記錄時,獲取當前version
  • 更新時,帶上這個version
  • 執行更新時, set version = newVersion where version = oldVersion
  • 如果version不對,就更新失敗

1.2 CAS算法

樂觀鎖的另一種技術技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。一般情況下是一個自旋操作,即不斷的重試

1、過程:

CAS 操作中包含三個操作數 :

  • 需要讀寫的內存位置V
  • 進行比較的預期原值A
  • 擬寫入的新值B

如果內存位置V的值與預期原值A相匹配,那麼處理器會自動將該位置值更新爲新值B。否則處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值)。CAS 有效地說明了“ 我認爲位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。 ”這其實和樂觀鎖的衝突檢查+數據更新的原理是一樣的。

2、 缺點

  • ABA問題

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

  • 循環時間長開銷大

自旋CAS(不成功,就一直循環執行,直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。

  • 只能保證一個共享變量的原子操作

當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操作。比如有兩個共享變量i = 2,j = a,合併一下ij = 2a,然後用CAS來操作ij。從Java 1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行CAS操作。

2、悲觀鎖

總是假設最壞的情況,每次取數據時都認爲其他線程會修改,所以都會加(悲觀)鎖。一旦加鎖,不同線程同時執行時,只能有一個線程執行,其他的線程在入口處等待,直到鎖被釋放(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

悲觀鎖在MySQLJava有廣泛的使用

  • MySQL的讀鎖、寫鎖、行鎖等
  • Javasynchronized關鍵字

3. 總結

讀的多,衝突機率小,樂觀鎖。
寫的多,衝突機率大,悲觀鎖。

_______________________________________________________

參考:

https://juejin.im/post/5b4977ae5188251b146b2fc8

https://segmentfault.com/a/1190000016611415

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章