三、樂觀鎖和悲觀鎖的使用場景
四、自旋鎖
總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖(共享資源每次只給乙個執行緒使用,其它執行緒阻塞,用完後再把資源轉讓給其它執行緒)。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
**總是假設最好的情況,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號機制和cas演算法實現。**樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。
1.版本號機制
一般是在資料表中加上乙個資料版本號version欄位,表示資料被修改的次數。當資料被修改時,version值會加一。當執行緒a要更新資料值時,在讀取資料的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前資料庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
舉乙個簡單的例子:
1.假設資料庫中帳戶資訊表中有乙個 version欄位,當前值為 1 ;而當前帳戶餘額字段( balance)為 $1002.cas演算法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 的操作結果的可能。
即compare and swap(比較與交換),是一種有名的無鎖演算法。無鎖程式設計,即不使用鎖的情況下實現多執行緒之間的變數同步,也就是在沒有執行緒被阻塞的情況下實現變數的同步,所以也叫非阻塞同步(non-blocking synchronization)。
cas演算法涉及到三個運算元:
讀寫的記憶體值 v
進行比較的值 e
擬寫入的新值 n
當且僅當 v 的值等於 e時,cas通過原子方式用新值n來更新v的值,否則不會執行任何操作(比較和替換是乙個原子操作)。一般情況下是乙個自旋操作,即不斷的重試。
1.假如現在有兩個執行緒t1,t2,他們各自的執行環境中都有共享變數的副本v1、v2,預期值e1、e2,預期主存中的值還沒有被改變.cas缺點1.迴圈時間太長;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(也就是不成功就一直迴圈執行直到成功)如果長時間不成功,會給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 悲觀鎖,正如其名,它指的是對資料被外界 包括本系統當前的其他事務,以及來自外部系統的事務處理 修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制 也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無...