Java开发技巧——并发控制中的乐观锁与悲观锁
1、為什么需要鎖?
在多用戶環(huán)境中,在同一時(shí)間可能會(huì)有多個(gè)用戶新相同的記錄,這會(huì)產(chǎn)生沖突。這就是的并發(fā)性問題。
2、典型的沖突類型:
(1)丟失新:一個(gè)事務(wù)的新覆蓋了其它事務(wù)的新結(jié)果,就是所謂的新丟失。例如:用戶A把值從6改為2,用戶B把值從2改為6,則用戶A丟失了他的新。
(2)臟讀:當(dāng)一個(gè)事務(wù)讀取其它完成一半事務(wù)的記錄時(shí),就會(huì)發(fā)生臟讀取。例如:用戶A,B看到的值都是6,用戶B把值改為2,用戶A讀到的值仍為6。
3、并發(fā)控制的機(jī)制
悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。
樂觀鎖:假設(shè)不會(huì)發(fā)生并發(fā)沖突,只在提交操作時(shí)檢查是否違反數(shù)據(jù)完整性。 樂觀鎖不能解決臟讀的問題。
4、樂觀鎖和悲觀鎖
悲觀鎖(Pessimistic Lock),就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在新的時(shí)候會(huì)判斷一下在此期間別人有沒有去新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫如果提供類似于write_condition機(jī)制的其實(shí)都是提供的樂觀鎖。
兩種鎖各有優(yōu)缺點(diǎn):
不可認(rèn)為一種好于另一種,像樂觀鎖適用于寫比較少的情況下,即沖突真的很少發(fā)生的時(shí)候,這樣可以省去了鎖的開銷,加大了系統(tǒng)的整個(gè)吞吐量。但如果經(jīng)常產(chǎn)生沖突,上層應(yīng)用會(huì)不斷的進(jìn)行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
5、樂觀鎖的應(yīng)用
1、使用自增長(zhǎng)的整數(shù)表示數(shù)據(jù)版本號(hào)。新時(shí)檢查版本號(hào)是否一致,比如數(shù)據(jù)庫中數(shù)據(jù)版本為6,新提交時(shí)version=6+1,使用該version值(=7)與數(shù)據(jù)庫version+1(=7)作比較,如果相等,則可以新,如果不等則有可能其他程序已新該記錄,所以返回錯(cuò)誤。
2、使用時(shí)間戳來實(shí)現(xiàn).
注:對(duì)于以上兩種方式,Hibernate自帶實(shí)現(xiàn)方式:在使用樂觀鎖的字段前加annotation: @Version, Hibernate在新時(shí)自動(dòng)校驗(yàn)該字段。
6、悲觀鎖的應(yīng)用
需要使用數(shù)據(jù)庫的鎖機(jī)制,比如SQL SERVER 的TABLOCKX(排它表鎖) 此選項(xiàng)被選中時(shí),SQL Server 將在整個(gè)表上置排它鎖直至該命令或事務(wù)結(jié)束。這將防止其他進(jìn)程讀取或修改表中的數(shù)據(jù)。
在實(shí)際生產(chǎn)環(huán)境里邊,如果并發(fā)量不大且不允許臟讀,可以使用悲觀鎖解決并發(fā)問題;但如果系統(tǒng)的并發(fā)非常大的話,悲觀鎖定會(huì)帶來非常大的性能問題,所以我們就要選擇樂觀鎖定的方法.
悲觀鎖會(huì)造成訪問數(shù)據(jù)庫時(shí)間較長(zhǎng),并發(fā)性不好,特別是長(zhǎng)事務(wù)。
樂觀鎖在現(xiàn)實(shí)中使用得較多,廠商較多采用。
一個(gè)典型的倚賴數(shù)據(jù)庫的悲觀鎖調(diào)用:
select * from account where name=”Erica” for update。
這條 sql 語句鎖定了account 表中所有符合檢索條件( name=”Erica” )的記錄。本次事務(wù)提交之前(事務(wù)提交時(shí)會(huì)釋放事務(wù)過程中的鎖),外界無法修改這些記錄。
Hibernate 的悲觀鎖,也是基于數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn)。
注意,只有在查詢開始之前(也就是Hiberate 生成 SQL 之前)設(shè)定加鎖,才會(huì)真正通過數(shù)據(jù)庫的鎖機(jī)制進(jìn)行加鎖處理,否則,數(shù)據(jù)已經(jīng)通過不包含 for update子句的 Select SQL 加載進(jìn)來,所謂數(shù)據(jù)庫加鎖也就無從談起。
樂觀鎖( Optimistic Locking )相對(duì)悲觀鎖而言,樂觀鎖機(jī)制采取了加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn),以操作大程度的獨(dú)占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開銷往往無法承受。
7、經(jīng)典案例分析
如一個(gè)金融系統(tǒng),當(dāng)某個(gè)操作員讀取用戶的數(shù)據(jù),并在讀出的用戶數(shù)據(jù)的基礎(chǔ)上進(jìn)行修改時(shí)(如改用戶帳戶余額),如果采用悲觀鎖機(jī)制,也就意味著整個(gè)操作過程中(從操作員讀出數(shù)據(jù)、開始修改直至提交修改結(jié)果的全過程,甚至還包括操作員中途去煮咖啡的時(shí)間),數(shù)據(jù)庫記錄始終處于加鎖狀態(tài),可以想見,如果面對(duì)幾百上千個(gè)并發(fā),這樣的情況將導(dǎo)致怎樣的后果。
樂觀鎖機(jī)制在一定程度上解決了這個(gè)問題。
樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個(gè) “version” 字段來實(shí)現(xiàn)。
讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以新,否則認(rèn)為是過期數(shù)據(jù)。
對(duì)于上面修改用戶帳戶信息的例子而言,假設(shè)數(shù)據(jù)庫中帳戶信息表中有一個(gè)version 字段,當(dāng)前值為 1 ;
而當(dāng)前帳戶余額字段( balance )為 $100 。
1、操作員 A 此時(shí)將其讀出( version=1 ),并從其帳戶余額中扣除 50(100-$50 )。
2、在操作員 A 操作的過程中,操作員 B 也讀入此用戶信息( version=1 ),并從其帳戶余額中扣除 20(100-$20 )。
3、操作員 A 完成了修改工作,將數(shù)據(jù)版本號(hào)加一( version=2 ),連同帳戶扣除后余額( balance=$50 ),提交至數(shù)據(jù)庫新,此時(shí)由于提交數(shù)據(jù)版本大于數(shù)據(jù)庫記錄當(dāng)前版本,數(shù)據(jù)被新,數(shù)據(jù)庫記錄 version 新為 2 。
4、操作員 B 完成了操作,也將版本號(hào)加一( version=2 )試圖向數(shù)據(jù)庫提交數(shù)據(jù)( balance=$80 ),但此時(shí)比對(duì)數(shù)據(jù)庫記錄版本時(shí)發(fā)現(xiàn),操作員 B 提交的數(shù)據(jù)版本號(hào)為 2 ,數(shù)據(jù)庫記錄當(dāng)前版本也為 2 ,不滿足 “ 提交版本必須大于記錄當(dāng)前版本才能執(zhí)行新 “ 的樂觀鎖策略,因此,操作員 B 的提交被駁回。這樣,就避免了操作員 B 用基于 version=1 的舊數(shù)據(jù)修改的結(jié)果覆蓋操作員 A 的操作結(jié)果的可能。
8、總結(jié)
從上面的例子可以看出,樂觀鎖機(jī)制避免了長(zhǎng)事務(wù)中的數(shù)據(jù)庫加鎖開銷(操作員 A和操作員 B 操作過程中,都沒有對(duì)數(shù)據(jù)庫數(shù)據(jù)加鎖),大大提升了大并發(fā)量下的系統(tǒng)整體性能表現(xiàn)。
需要注意的是,樂觀鎖機(jī)制往往基于系統(tǒng)中的數(shù)據(jù)存儲(chǔ)邏輯,因此也具備一定的局限性,如在上例中,由于樂觀鎖機(jī)制是在我們的系統(tǒng)中實(shí)現(xiàn),來自外部系統(tǒng)的用戶余額新操作不受我們系統(tǒng)的控制,因此可能會(huì)造成臟數(shù)據(jù)被新到數(shù)據(jù)庫中。在系統(tǒng)設(shè)計(jì)階段,我們應(yīng)該充分考慮到這些情況出現(xiàn)的可能性,并進(jìn)行相應(yīng)調(diào)整(如將樂觀鎖策略在數(shù)據(jù)庫存儲(chǔ)過程中實(shí)現(xiàn),對(duì)外只開放基于此存儲(chǔ)過程的數(shù)據(jù)新途徑,而不是將數(shù)據(jù)庫表直接對(duì)外公開)。
本文來自千鋒教育,轉(zhuǎn)載請(qǐng)注明出處。
總結(jié)
以上是生活随笔為你收集整理的Java开发技巧——并发控制中的乐观锁与悲观锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10个常用的Python图像处理工具,建
- 下一篇: Java常用的5大框架介绍!