樂觀鎖:拿資料的時候都認為在使用該資料的過程中,別人不會修改它,所以在此過程中不會上鎖。而當更新資料之後,會判斷在此期間有沒有其他人更改這個資料。
悲觀鎖:拿資料的時候都認為在使用過程中,別人會修改它,所以一開始就會上鎖,別人想拿該資料就會阻塞,直到獲取到鎖。(共享資源只給乙個執行緒使用,其他執行緒阻塞,直到資源使用完後釋放鎖,其他執行緒才有許可權訪問該資源)
樂觀鎖:適用於多讀的型別,可以提高吞吐量。
悲觀鎖:適用於多寫的場景,不會像樂觀鎖那樣出現很多衝突。
可能出現的問題:
樂觀鎖:
1、如果在併發量比較高的情況下,如果許多執行緒反覆嘗試更新同一共享變數,卻又一直更新不成功,迴圈往復,會給cpu帶來很大的壓力。
2、不能保證**塊的原子性。
3、aba問題。
悲觀鎖:
1、在多執行緒競爭下,加鎖,釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題。
2、乙個執行緒持有鎖會導致其他所有需要此鎖的執行緒阻塞。
3、如果乙個優先順序高的執行緒等待乙個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能風險。
cas(compare and swap):
更新乙個變數的時候,只有當變數的預期值a和記憶體位址v當中的實際值相同時,才會將記憶體位址v對應的值修改為b。
aba問題,就銀行訪問款場景舉例:
1、a在銀行有20元,然後a需要取5元,但是實際上a的取款操作提交了兩次,開啟了兩個取款的執行緒(或goroutine)。
2、如果乙個執行緒成功,乙個失敗倒沒什麼關係。但是有可能乙個執行緒成功,乙個被阻塞。
3、b往這個賬戶存了5元。按理講,這個時候賬戶應該還是20元。
4、但是之前被阻塞的取款任務重新被執行,看到賬戶是20元,記憶體位址值與預期值相同,然後接著做了取款5元的操作。結果賬戶又變成15元了,b的存款工作白做了。
解決該問題的辦法是對每一項操作有乙個版本號字段,這樣在上面第四步時,看到版本號與自己的不一致,那麼就能識別出aba問題,然後不執行取款操作。
下面是基於golang語言的樂觀鎖與悲觀鎖的測試**,參考了這篇文章,**實現簡潔易懂,所以我沒有做太多改動。
其中使用的sync.mutex
是悲觀鎖,使用的atomic
是樂觀鎖。**列舉了有衝突和無衝突的情況下,樂觀鎖與悲觀鎖的執行效率。
在有較小衝突(少量goroutine)的情況下,樂觀鎖與悲觀鎖效率相近,當使用多個goroutine的時候,樂觀鎖的效率明顯高於悲觀鎖(下面的注釋**,有8個goroutine,悲觀鎖的效率低很多)。同樣,當沒有衝突的時候,樂觀鎖的效能依然很好。
import
("fmt"
"sync"
"time"
"sync/atomic"
)var wg sync.waitgroup
var lock sync.mutex
var times =
10000000
func
add(x *
int)
wg.done()
}func
sub(x *
int)
wg.done()
}func
addmutex
(x *
int)
wg.done()
}func
submutex
(x *
int)
wg.done()
}func
addatomic
(x *
int32
) wg.
done()
}func
subatomic
(x *
int32
) wg.
done()
}func
main()
Hibernate鎖機制(悲觀鎖,樂觀鎖)
鎖 locking 業務邏輯的實現過程中,往往需要保證資料訪問的排他性。如在金融系統的日終結算處理中,我們希望針對某個cut off時間點的資料進行處理,而不希望在結算進行過程中 可能是幾秒種,也可能是幾個小時 資料再發生變化。此時,我們就需要通過一些機制來保證這些資料在某個操作過程中不會被外界修改...
悲觀鎖和樂觀鎖機制
由於併發的存在,當多個執行緒同時對資料庫的同一資料進行刪改查操作時,資料可能會不準確,因此資料庫會有行級鎖的概念。資料庫的行級鎖就是採用一種獨佔的方式,只要當前有乙個執行緒操作這條資料,那麼其他執行緒對該資料只有查詢的能力,沒有修改的權利,因此行級鎖具有排他性,這樣保證了資料的一致性和安全性。資料庫...
悲觀鎖與樂觀鎖以及樂觀鎖的實現
總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每 次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳 統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫 鎖等,都是在做操作之前先上鎖。顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所...