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

鎖(locking)

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

悲觀鎖(Pessimistic Locking)

悲觀鎖痹束,正如其名检疫,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(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”)的記錄。
本次事務(wù)提交之前(事務(wù)提交時會釋放事務(wù)過程中的鎖)荧降,外界無法修改這些記錄接箫。

樂觀鎖(Optimistic Locking)

相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制朵诫。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn)辛友,以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷剪返,特別是對長事務(wù)而言废累,這樣的開銷往往無法承受。
一段執(zhí)行邏輯加上樂觀鎖,不同線程同時執(zhí)行時,可以同時進入執(zhí)行,在最后更新數(shù)據(jù)的時候要檢查這些數(shù)據(jù)是否被其他線程修改了(版本和執(zhí)行初是否相同),沒有修改則進行更新,否則放棄本次操作脱盲。樂觀鎖機制在一定程度上解決了這個問題邑滨。樂觀鎖,大多是基于數(shù)據(jù)版本(Version)記錄機制實現(xiàn)钱反。何謂數(shù)據(jù)版本掖看?即為數(shù)據(jù)增加一個版本標(biāo)識匣距,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個“version”字段來實現(xiàn)哎壳。
讀取出數(shù)據(jù)時毅待,將此版本號一同讀出,之后更新時耳峦,對此版本號加一恩静。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進行比對蹲坷,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當(dāng)前版本號驶乾,則予以更新,否則認(rèn)為是過期數(shù)據(jù)循签。
對于上面修改用戶帳戶信息的例子而言级乐,假設(shè)數(shù)據(jù)庫中帳戶信息表中有一個version字段,當(dāng)前值為1县匠;而當(dāng)前帳戶余額字段(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ù)庫記錄當(dāng)前版本,數(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ù)庫記錄當(dāng)前版本也為2二跋,不滿足“提交版本必須大于記錄當(dāng)前版本才能執(zhí)行更新“的樂觀鎖策略,因此流昏,操作員B 的提交被駁回扎即。
這樣吞获,就避免了操作員B 用基于version=1 的舊數(shù)據(jù)修改的結(jié)果覆蓋操作員A的操作結(jié)果的可能。

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

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

CAS是樂觀鎖技術(shù),當(dāng)多個線程嘗試使用CAS同時更新同一個變量時闷营,只有其中一個線程能更新變量的值烤黍,而其它線程都失敗,失敗的線程并不會被掛起傻盟,而是被告知這次競爭中失敗速蕊,并可以再次嘗試。

CAS 操作中包含三個操作數(shù) —— 需要讀寫的內(nèi)存位置(V)娘赴、進行比較的預(yù)期原值(A)和擬寫入的新值(B)规哲。如果內(nèi)存位置V的值與預(yù)期原值A(chǔ)相匹配,那么處理器會自動將該位置值更新為新值B诽表。否則處理器不做任何操作唉锌。無論哪種情況,它都會在 CAS 指令之前返回該位置的值竿奏。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功袄简,而不提取當(dāng)前值。)CAS 有效地說明了“ 我認(rèn)為位置 V 應(yīng)該包含值 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 方法,該方法的作用相當(dāng)于 ++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 后的結(jié)果進行CAS操作,如果成功就返回結(jié)果返帕,否則重試直到成功為止桐玻。
而 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)的链韭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末偏竟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子梧油,更是在濱河造成了極大的恐慌苫耸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件儡陨,死亡現(xiàn)場離奇詭異褪子,居然都是意外死亡,警方通過查閱死者的電腦和手機骗村,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門嫌褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胚股,你說我怎么就攤上這事笼痛。” “怎么了琅拌?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵缨伊,是天一觀的道長。 經(jīng)常有香客問我进宝,道長刻坊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任党晋,我火速辦了婚禮谭胚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘未玻。我一直安慰自己灾而,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布扳剿。 她就那樣靜靜地躺著旁趟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庇绽。 梳的紋絲不亂的頭發(fā)上轻庆,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天癣猾,我揣著相機與錄音,去河邊找鬼余爆。 笑死纷宇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛾方。 我是一名探鬼主播像捶,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼桩砰!你這毒婦竟也來了拓春?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤亚隅,失蹤者是張志新(化名)和其女友劉穎硼莽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體煮纵,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡懂鸵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了行疏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匆光。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酿联,靈堂內(nèi)的尸體忽然破棺而出终息,到底是詐尸還是另有隱情,我是刑警寧澤贞让,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布周崭,位于F島的核電站,受9級特大地震影響喳张,放射性物質(zhì)發(fā)生泄漏续镇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一蹲姐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧人柿,春花似錦柴墩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哥放,卻和暖如春歼指,著一層夾襖步出監(jiān)牢的瞬間爹土,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工踩身, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胀茵,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓挟阻,卻偏偏與公主長得像琼娘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子附鸽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容