樂觀鎖 悲觀鎖 自旋鎖

2021-10-07 02:14:53 字數 4722 閱讀 3654

三、樂觀鎖和悲觀鎖的使用場景

四、自旋鎖

總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖(共享資源每次只給乙個執行緒使用,其它執行緒阻塞,用完後再把資源轉讓給其它執行緒)。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

**總是假設最好的情況,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號機制和cas演算法實現。**樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。

1.版本號機制

一般是在資料表中加上乙個資料版本號version欄位,表示資料被修改的次數。當資料被修改時,version值會加一。當執行緒a要更新資料值時,在讀取資料的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前資料庫中的version值相等時才更新,否則重試更新操作,直到更新成功。

舉乙個簡單的例子:

1.假設資料庫中帳戶資訊表中有乙個 version欄位,當前值為 1 ;而當前帳戶餘額字段( balance)為 $100

2.當需要對賬戶資訊表進行更新的時候,需要首先讀取version欄位。

3.操作員 a 此時將其讀出( version=1),並從其帳戶餘額中扣除 $50( $100-$50 )。

4. 在操作員 a操作的過程中,操作員b 也讀入此使用者資訊( version=1),並從其帳戶餘額中扣除 $20 ( $100-$20 )。

5. 操作員 a完成了修改工作,提交更新之前會先看資料庫的版本和自己讀取到的版本是否一致,一致的話,就會將資料版本號加1( version=2),連同帳戶扣除後餘額( $balance=50),提交至資料庫更新,

6.此時由於提交資料版本大於資料庫記錄當前版本,資料被更新,資料庫記錄version更新為 2。

7.操作員 b完成了操作,提交更新之前會先看資料庫的版本和自己讀取到的版本是否一致,但此時比對資料庫記錄版本時發現,操作員 b 提交的資料版本號為 2,而自己讀取到的版本號為1,不滿足 「 當前最後更新的version與操作員第一次讀取的版本號相等 「 的樂觀鎖策略,因此,操作員 b的提交被駁回。

8.這樣,就避免了操作員 b 用基於 version=1的舊資料修改的結果覆蓋操作員a 的操作結果的可能。

2.cas演算法

即compare and swap(比較與交換),是一種有名的無鎖演算法。無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步,所以也叫非阻塞同步(non-blocking synchronization)。

cas演算法涉及到三個運算元:

讀寫的記憶體值 v

進行比較的值 e

擬寫入的新值 n

當且僅當 v 的值等於 e時,cas通過原子方式用新值n來更新v的值,否則不會執行任何操作(比較和替換是乙個原子操作)。一般情況下是乙個自旋操作,即不斷的重試。

1.假如現在有兩個執行緒t1,t2,他們各自的執行環境中都有共享變數的副本v1、v2,預期值e1、e2,預期主存中的值還沒有被改變.

2.假設現在在併發環境,並且t1先拿到了執行許可權,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次發起嘗試.

3.然後t1比較預期值e1和主存中的v,發現e1=v,說明預期值是正確的,執行n1=v1+1,並將n1的值傳入主存。這時候貯存中的v=21.

4.然後t2又緊接著拿到了執行權,比較e2和主存v的值,由於v已經被t1改為21,所以e2!=v,t2執行緒將主存中已經改變的值更新到自己的副本中,再發起重試;

5.直到預期值等於主存中的值,說明沒有別的執行緒對舊值進行修改,繼續執行**,退出;

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.什麼時候使用樂觀鎖?

資源提交衝突,其他使用方需要重新讀取資源,會增加讀的次數,但是可以面對高併發場景,前提是如果出現提交失敗,使用者是可以接受的。因此一般樂觀鎖只用在高併發、多讀少寫的場景。

2.什麼時候使用悲觀鎖?

一旦通過悲觀鎖鎖定乙個資源,那麼其他需要操作該資源的使用方,只能等待直到鎖被釋放,好處在於可以減少併發,但是當併發量非常大的時候,由於鎖消耗資源,並且可能鎖定時間過長,容易導致系統效能下降,資源消耗嚴重。因此一般我們可以在併發量不是很大,並且出現併發情況導致的異常使用者和系統都很難以接受的情況下,會選擇悲觀鎖進行。

總結:cas(比較並交換)是cpu指令級的操作,只有一步原子操作,所以非常快。而且cas避免了請求作業系統來裁定鎖的問題,不需要進入核心,不需要切換執行緒,操作自旋機率較少,因此可以獲得更高的效能不用麻煩作業系統,直接在cpu內部就搞定了

1.何謂自旋鎖?它是為實現保護共享資源而提出一種鎖機制。

2.其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有乙個保持者,也就說,在任何時刻最多只能有乙個執行單元獲得鎖。

3.但是兩者在排程機制上略有不同。

4.對於互斥鎖,如果資源已經被占用,資源申請者只能進入睡眠狀態

5.但是自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈在那裡看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名

1.自旋鎖的原理

跟互斥鎖一樣,乙個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。

如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麼將立即得到鎖;

如果在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操作將自旋在那裡,一直去嘗試獲取鎖,直到該自旋鎖的保持者釋放了鎖。

2.自旋鎖的缺陷

1.死鎖。試圖遞迴地獲得自旋鎖必然會引起死鎖:遞迴程式的持有例項在第二個例項迴圈,以試圖獲得相同自旋鎖時,不會釋放此自旋鎖。在遞迴程式中使用自旋鎖應遵守下列策略:遞迴程式決不能在持有自旋鎖時呼叫它自己,也決不能在遞迴呼叫時試圖獲得相同的自旋鎖。此外如果乙個程序已經將資源鎖定,那麼,即使其它申請這個資源的程序不停地瘋狂「自旋」,也無法獲得資源,從而進入死迴圈。

2.過多占用cpu資源。如果不加限制,由於申請鎖的執行緒一直在迴圈等待,因此自旋鎖在鎖定的時候,如果不成功,不會睡眠,會持續的嘗試,單cpu的時候自旋鎖會讓其它process動不了. 因此,一般自旋鎖實現會有乙個引數限定最多持續嘗試次數. 超出後, 自旋鎖放棄當前time slice. 等下一次機會

3.自旋鎖的使用場景

自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況

正是由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。

訊號量和讀寫訊號量適合於保持時間較長的情況,它們會導致呼叫者睡眠,因此只能在程序上下文使用,而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。

如果被保護的共享資源只在程序上下文訪問,使用訊號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。

但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和頂半部即軟中斷),就必須使用自旋鎖。

自旋鎖保持期間是搶占失效的,而訊號量和讀寫訊號量保持期間是可以被搶占的自旋鎖只有在核心可搶占或smp(多處理器)的情況下才真正需要,在單cpu且不可搶占的核心下,自旋鎖的所有操作都是空操作

互斥鎖 自旋鎖 讀寫鎖 悲觀鎖 樂觀鎖

最底層的兩種就是會 互斥鎖和自旋鎖 有很多高階的鎖都是基於它們實現的,你可以認為它們是各種鎖的地基,所以我們必須清楚它倆之間的區別和應用。加鎖的目的就是保證共享資源在任意時間裡,只有乙個執行緒訪問,這樣就可以避免多執行緒導致共享資料錯亂的問題。當已經有乙個執行緒加鎖後,其他執行緒加鎖則就會失敗,互斥...

作業系統 互斥鎖 自旋鎖 讀寫鎖 悲觀鎖 樂觀鎖

使用場景 如果你能確定被鎖住的 執行時間很長,就不應該用互斥鎖 加鎖的目的就是保證共享資源在任意時間裡,只有乙個執行緒訪問,這樣就可以避免多執行緒導致共享資料錯亂的問題。互斥鎖加鎖失敗後,執行緒會釋放 cpu 給其他執行緒,自身處於獲取鎖阻塞狀態,然後從使用者態切換到核心態由由核心幫助進行切換執行緒...

悲觀鎖樂觀鎖

1 悲觀鎖,正如其名,它指的是對資料被外界 包括本系統當前的其他事務,以及來自外部系統的事務處理 修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制 也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無...