Java 進階 & Java 中的悲觀鎖和樂觀鎖的實現(xiàn)

鎖(locking)

業(yè)務邏輯的實現(xiàn)過程中,往往需要保證數(shù)據(jù)訪問的排他性。如在金融系統(tǒng)的日終結算處理中,我們希望針對某個cut-off時間點的數(shù)據(jù)進行處理,而不希望在結算進行過程中(可能是幾秒種,也可能是幾個小時),數(shù)據(jù)再發(fā)生變化。此時,我們就需要通過一些機制來保證這些數(shù)據(jù)在某個操作過程中不會被外界修改,這樣的機制,在這里,也就是所謂的“鎖”,即給我們選定的目標數(shù)據(jù)上鎖,使其無法被其他程序修改。

悲觀鎖(Pessimistic Locking)

悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當前的其他事務,以及來自外部系統(tǒng)的事務處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
一段執(zhí)行邏輯加上悲觀鎖,不同線程同時執(zhí)行時,只能有一個線程執(zhí)行,其他的線程在入口處等待,直到鎖被釋放。
一個典型的倚賴數(shù)據(jù)庫的悲觀鎖調(diào)用:
select * from account where name=”Erica” for update
這條sql 語句鎖定了account 表中所有符合檢索條件(name=”Erica”)的記錄。
本次事務提交之前(事務提交時會釋放事務過程中的鎖),外界無法修改這些記錄。

樂觀鎖(Optimistic Locking)

相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
一段執(zhí)行邏輯加上樂觀鎖,不同線程同時執(zhí)行時,可以同時進入執(zhí)行,在最后更新數(shù)據(jù)的時候要檢查這些數(shù)據(jù)是否被其他線程修改了(版本和執(zhí)行初是否相同),沒有修改則進行更新,否則放棄本次操作。樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數(shù)據(jù)版本(Version)記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個“version”字段來實現(xiàn)。
讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應記錄的當前版本信息進行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當前版本號,則予以更新,否則認為是過期數(shù)據(jù)。
對于上面修改用戶帳戶信息的例子而言,假設數(shù)據(jù)庫中帳戶信息表中有一個version字段,當前值為1;而當前帳戶余額字段(balance)為100。 1、 操作員A 此時將其讀出(version=1),并從其帳戶余額中扣除50(100-50)。
2 、在操作員A操作的過程中,操作員B也讀入此用戶信息(version=1),并從其帳戶余額中扣除20(100-20)。 3 、操作員A完成了修改工作,將數(shù)據(jù)版本號加一(version=2),連同帳戶扣除后余額(balance=50),提交至數(shù)據(jù)庫更新,此時由于提交數(shù)據(jù)版本大于數(shù)據(jù)庫記錄當前版本,數(shù)據(jù)被更新,數(shù)據(jù)庫記錄version更新為2。
4 、操作員B完成了操作,也將版本號加一(version=2)試圖向數(shù)據(jù)庫提交數(shù)據(jù)(balance=$80),但此時比對數(shù)據(jù)庫記錄版本時發(fā)現(xiàn),操作員B提交的數(shù)據(jù)版本號為2,數(shù)據(jù)庫記錄當前版本也為2,不滿足“提交版本必須大于記錄當前版本才能執(zhí)行更新“的樂觀鎖策略,因此,操作員B 的提交被駁回。
這樣,就避免了操作員B 用基于version=1 的舊數(shù)據(jù)修改的結果覆蓋操作員A的操作結果的可能。

從解釋上可以看出,悲觀鎖具有很強的獨占性,也是最安全的.而樂觀鎖很開放,效率高,安全性比悲觀鎖低,因為在樂觀鎖檢查數(shù)據(jù)版本一致性時也可能被其他線程修改數(shù)據(jù).從下面的例子中可以看出來這里說的安全差別.

上面提到的樂觀鎖的概念中其實已經(jīng)闡述了它的具體實現(xiàn)細節(jié):主要就是兩個步驟:沖突檢測和數(shù)據(jù)更新****。其實現(xiàn)方式有一種比較典型的就是 Compare and Swap ( CAS )。

CAS是樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。

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

JAVA對CAS的支持:

在JDK1.5 中新增 java.util.concurrent (J.U.C)就是建立在CAS之上的。相對于對于 synchronized 這種阻塞算法,CAS是非阻塞算法的一種常見實現(xiàn)。所以J.U.C在性能上有了很大的提升。
以 java.util.concurrent 中的 AtomicInteger 為例,看一下在不使用鎖的情況下是如何保證線程安全的。主要理解 getAndIncrement 方法,該方法的作用相當于 ++i 操作。

public class AtomicInteger extends Number implements java.io.Serializable { 
private volatile int value;
public final int get() { 
return value; 
 } 

 public final int getAndIncrement() { 
 for (;;) { 
int current = get(); 
int next = current + 1; 
 if (compareAndSet(current, next)) 
 return current; 
} 
} 

public final boolean compareAndSet(int expect, int update) { 
return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 
 } 
 }

在沒有鎖的機制下,字段value要借助volatile原語,保證線程間的數(shù)據(jù)是可見性。這樣在獲取變量的值的時候才能直接讀取。然后來看看 ++i 是怎么做到的。
getAndIncrement 采用了CAS操作,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和 +1 后的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。
而 compareAndSet 利用JNI(Java Native Interface)來完成CPU指令的操作:

public final boolean compareAndSet(int expect, int update) {  
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}   

其中unsafe.compareAndSwapInt(this, valueOffset, expect, update);
類似如下邏輯:

if (this == expect) {
 this = update
return true;
} else {
 return false;
 }

那么比較this == expect,替換this = update,compareAndSwapInt實現(xiàn)這兩個步驟的原子性呢? 參考CAS的原理
CAS原理:
CAS通過調(diào)用JNI的代碼實現(xiàn)的。而compareAndSwapInt就是借助C來調(diào)用CPU底層指令實現(xiàn)的。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容