1. 樂觀鎖
樂觀鎖顧名思義就是在操作時很樂觀,認為操作不會產生並發問題(不會有其他線程對數據進行修改),因此不會上鎖。但是在更新時會判斷其他線程在這之前有沒有對數據進行修改,一般會使用版本號機制或CAS(compare and swap)算法實現。
簡單理解:這里的數據,別想太多,你盡管用,出問題了算我慫,即操作失敗后事務回滾、提示。版本號、CAS這2種方法本質上是一樣的:假如滿足條件,做你想做的事,條件判斷是原子的或者是快速的,耗時幾乎不計。
1.1 版本號機制
1.1.1 實現套路:
- 取出記錄時,獲取當前
version - 更新時,帶上這個
version - 執行更新時,
set version = newVersion where version = oldVersion - 如果
version不對,就更新失敗
核心SQL:
update table set name = 'Aron', version = version + 1 where id = #{id} and version = #{version};
1.1.2 實例-Mybatis-plus 樂觀鎖實現
原文查看請點擊 Mybatis-plus 樂觀鎖實現
1.2 CAS算法
樂觀鎖的另一種技術技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。
CAS 操作中包含三個操作數 :
- 需要讀寫的內存位置
V - 進行比較的預期原值
A - 擬寫入的新值
B
如果內存位置V的值與預期原值A相匹配,那么處理器會自動將該位置值更新為新值B。否則處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值)。CAS 有效地說明了“ 我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。 ”這其實和樂觀鎖的沖突檢查+數據更新的原理是一樣的。
1.2.1 實例-concurrent包的實現
由於java的CAS同時具有 volatile 讀和volatile寫的內存語義,因此Java線程之間的通信現在有了下面四種方式:
A線程寫volatile變量,隨后B線程讀這個volatile變量。A線程寫volatile變量,隨后B線程用CAS更新這個volatile變量。A線程用CAS更新一個volatile變量,隨后B線程用CAS更新這個volatile變量。A線程用CAS更新一個volatile變量,隨后B線程讀這個volatile變量。
Java的CAS會使用現代處理器上提供的高效機器級別原子指令,這些原子指令以原子方式對內存執行讀-改-寫操作,這是在多處理器中實現同步的關鍵(從本質上來說,能夠支持原子性讀-改-寫指令的計算機器,是順序計算圖靈機的異步等價機器,因此任何現代的多處理器都會去支持某種能對內存執行原子性讀-改-寫操作的原子指令)。同時,volatile變量的讀/寫和CAS可以實現線程之間的通信。把這些特性整合在一起,就形成了整個concurrent包得以實現的基石。
仔細分析concurrent包的源代碼實現,會發現一個通用化的實現模式:
- 首先,聲明共享變量為
volatile; - 然后,使用CAS的原子條件更新來實現線程之間的同步;
- 同時,配合以
volatile的讀/寫和CAS所具有的volatile讀和寫的內存語義來實現線程
1.2.2 缺點
ABA問題
比如說一個線程T1從內存位置V中取出A,這時候另一個線程T2也從內存中取出A,並且T2進行了一些操作變成了B,然后T2又將V位置的數據變成A,這時候線程T1進行CAS操作發現內存中仍然是A,然后T1操作成功。盡管線程T1的CAS操作成功,但可能存在潛藏的問題。
- 循環時間長開銷大
自旋CAS(不成功,就一直循環執行,直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
- 只能保證一個共享變量的原子操作
當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合並成一個共享變量來操作。比如有兩個共享變量i = 2,j = a,合並一下ij = 2a,然后用CAS來操作ij。從Java 1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。
2. 悲觀鎖
總是假設最壞的情況(想法很悲觀),每次取數據時都認為其他線程會修改,所以都會加(悲觀)鎖。一旦加鎖,不同線程同時執行時,只能有一個線程執行,其他的線程在入口處等待,直到鎖被釋放。
悲觀鎖在MySQL、Java有廣泛的使用
MySQL的讀鎖、寫鎖、行鎖等Java的synchronized關鍵字
3. 總結
讀的多,沖突幾率小,樂觀鎖。
寫的多,沖突幾率大,悲觀鎖。
