乐观锁悲观锁自旋锁
樂觀鎖&悲觀鎖&自旋鎖
文章目錄
- 樂觀鎖&悲觀鎖&自旋鎖
- 一、悲觀鎖
- 二、樂觀鎖
- 1.樂觀鎖常見的兩種實現方式
- 2. 版本號機制
- 3. CAS算法
- 4. CAS缺點
- 四、樂觀鎖和悲觀鎖的使用場景
- 五、自旋鎖
- 1.自旋鎖的原理
- 2.自旋鎖的缺陷
- 3.自旋鎖的使用場景
一、悲觀鎖
總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程)。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
二、樂觀鎖
總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似于write_condition機制,其實都是提供的樂觀鎖。
1.樂觀鎖常見的兩種實現方式
2. 版本號機制
一般是在數據表中加上一個數據版本號version字段,表示數據被修改的次數。當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前數據庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
舉一個簡單的例子:
1.假設數據庫中帳戶信息表中有一個 versionversionversion 字段,當前值為 111 ;而當前帳戶余額字段( balancebalancebalance )為 $100100100
2.當需要對賬戶信息表進行更新的時候,需要首先讀取version字段。
3.操作員 AAA 此時將其讀出( version=1version=1version=1 ),并從其帳戶余額中扣除 $50( $100-$50 )。
4. 在操作員 AAA操作的過程中,操作員BBB 也讀入此用戶信息( version=1version=1version=1),并從其帳戶余額中扣除 $20 ( $100-$20 )。
5. 操作員 AAA完成了修改工作,提交更新之前會先看數據庫的版本和自己讀取到的版本是否一致,一致的話,就會將數據版本號加1( version=2version=2version=2),連同帳戶扣除后余額( $balance=505050 ),提交至數據庫更新,
6.此時由于提交數據版本大于數據庫記錄當前版本,數據被更新,數據庫記錄
versionversionversion 更新為 222。 操作員 BBB
7.完成了操作,提交更新之前會先看數據庫的版本和自己讀取到的版本是否一致,但此時比對數據庫記錄版本時發現,操作員 BBB 提交的數據版本號為 222
,而自己讀取到的版本號為111 ,不滿足 “ 當前最后更新的versionversionversion與操作員第一次讀取的版本號相等 “ 的樂觀鎖策略,因此,操作員 B的提交被駁回。
8.這樣,就避免了操作員 BBB 用基于 version=1version=1version=1 的舊數據修改的結果覆蓋操作員A 的操作結果的可能。
3. CAS算法
即compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操作數:
- 需要讀寫的內存值 V
- 進行比較的值 E
- 擬寫入的新值 N
當且僅當 V 的值等于 A時,CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。
4. CAS缺點
1.循環時間太長;
自旋CAS(也就是不成功就一直循環執行直到成功)如果長時間不成功,會給CPU帶來非常大的執行開銷。 如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決于具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
2.只能保證一個共享變量原子操作;
CAS 只對單個共享變量有效,當操作涉及跨多個共享變量時 CAS 無效。但是從 JDK 1.5開始,提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行 CAS 操作.所以我們可以使用鎖或者利用AtomicReference類把多個共享變量合并成一個共享變量來操作。
3.會出現ABA問題;
如果一個變量V初次讀取的時候是A值,并且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的,因為在這段時間它的值可能被改為其他值,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。這個問題被稱為CAS操作的 "ABA"問題。
JDK 1.5 以后的 AtomicStampedReference 類就提供了此種能力,其中的 compareAndSet 方法就是 首先檢查當前引用是否等于預期引用,并且當前標志是否等于預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值
四、樂觀鎖和悲觀鎖的使用場景
1.什么時候使用樂觀鎖?
資源提交沖突,其他使用方需要重新讀取資源,會增加讀的次數,但是可以面對高并發場景,前提是如果出現提交失敗,用戶是可以接受的。因此一般樂觀鎖只用在高并發、多讀少寫的場景。
其中:GIT,SVN,CVS等代碼版本控制管理器,就是一個樂觀鎖使用很好的場景,例如:A、B程序員,同時從SVN服務器上下載了code.html文件,當A完成提交后,此時B再提交,那么會報版本沖突,此時需要B進行版本處理合并后,再提交到服務器。這其實就是樂觀鎖的實現全過程。如果此時使用的是悲觀鎖,那么意味者所有程序員都必須一個一個等待操作提交完,才能訪問文件,這是難以接受的。
2.什么時候使用悲觀鎖?
一旦通過悲觀鎖鎖定一個資源,那么其他需要操作該資源的使用方,只能等待直到鎖被釋放,好處在于可以減少并發,但是當并發量非常大的時候,由于鎖消耗資源,并且可能鎖定時間過長,容易導致系統性能下降,資源消耗嚴重。因此一般我們可以在并發量不是很大,并且出現并發情況導致的異常用戶和系統都很難以接受的情況下,會選擇悲觀鎖進行。
總結:
CAS(比較并交換)是CPU指令級的操作,只有一步原子操作,所以非常快。而且CAS避免了請求操作系統來裁定鎖的問題,不需要進入內核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能不用麻煩操作系統,直接在CPU內部就搞定了
五、自旋鎖
1.自旋鎖的原理
2.自旋鎖的缺陷
3.自旋鎖的使用場景
自旋鎖比較適用于鎖使用者保持鎖時間比較短的情況。
- 正是由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高于互斥鎖。
- 信號量和讀寫信號量適合于保持時間較長的情況,它們會導致調用者睡眠,因此只能在進程上下文使用,而自旋鎖適合于保持時間非常短的情況,它可以在任何上下文使用。
- 如果被保護的共享資源只在進程上下文訪問,使用信號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。
- 但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。
- 自旋鎖保持期間是搶占失效的,而信號量和讀寫信號量保持期間是可以被搶占的。自旋鎖只有在內核可搶占或SMP(多處理器)的情況下才真正需要,在單CPU且不可搶占的內核下,自旋鎖的所有操作都是空操作。
總結
- 上一篇: 在线OJ项目(三)
- 下一篇: HTTP和HTTPS总结