目錄
1.悲觀鎖vs樂觀鎖
關鍵總結
悲觀鎖:
樂觀鎖:
選擇建議
用?悲觀鎖?當:
用?樂觀鎖?當:
2.重量級鎖vs輕量級鎖?
選擇建議
用?輕量級鎖:
用?重量級鎖:
3.掛起等待鎖vs自旋鎖
?關鍵細節說明
選擇建議
4.普通互斥鎖vs讀寫鎖
? ? ? ?讀寫鎖互斥規則詳解
? ? ? ?普通互斥鎖 vs 讀寫鎖對比
5.可重入鎖vs不可重入鎖?
選擇建議
6.公平鎖vs不公平鎖
1)?公平鎖和不公平鎖的對比
2)選擇建議
7.鎖升級
8.鎖優化
9.鎖的粗化
10.synchronized
11.reentrantlock和synchronized的對比
鎖策略(Locking Strategies)是多線程編程中用于控制并發訪問共享資源的核心機制。不同的鎖策略在性能、公平性、復雜度等方面有顯著差異。
以下來介紹幾組常見的鎖策略:
1.悲觀鎖vs樂觀鎖
悲觀鎖:在加鎖的時候預測接下來的競爭會非常激烈,所以就需要針對這種激烈的競爭情況做一些準備工作:每次拿數據的時候都會上鎖,這樣別人想要拿數據的時候就會阻塞知道它拿到鎖。
樂觀鎖:在加鎖的時候預測接下來的競爭不會非常激烈,也就一般不會發生并發沖突,不必多做準備。在數據進行提交更新的時候再檢測是否產生并發沖突。
特性 | 悲觀鎖 | 樂觀鎖 |
---|---|---|
核心思想 | 假設并發沖突一定會發生,因此先加鎖再訪問數據。 | 假設并發沖突較少發生,因此不加鎖,通過版本號/CAS機制檢測沖突。 |
實現方式 | 直接加鎖(如?synchronized 、ReentrantLock 、數據庫?SELECT FOR UPDATE )。 | 無鎖機制(如?CAS 、版本號校驗、AtomicXXX ?類、數據庫?樂觀并發控制 )。 |
阻塞行為 | ??會阻塞其他線程(未獲取鎖的線程必須等待)。 | ??無阻塞,線程直接操作數據,沖突時通過重試或回滾解決。 |
適用場景 | 寫多讀少(如銀行轉賬、庫存扣減等強一致性場景)。 | 讀多寫少(如緩存、統計計數等低沖突場景)。 |
性能開銷 | 高(鎖競爭、上下文切換、死鎖風險)。 | 低(無鎖競爭,但沖突頻繁時重試開銷增大)。 |
數據一致性 | 強一致性(鎖內操作串行化)。 | 最終一致性(沖突時需業務層處理)。 |
典型實現 | Java:?synchronized 、ReentrantLock 數據庫:? SELECT FOR UPDATE 。 | Java:?AtomicInteger 、StampedLock 數據庫:? 版本號字段 、CAS操作 。 |
優缺點 | ? 保證強一致性 ? 吞吐量低,可能死鎖。 | ? 高并發性能 ? 需處理沖突,可能活鎖或重試饑餓。 |
關鍵總結
-
悲觀鎖:
-
“先保護再操作”,適合強一致性場景,但性能較差。
-
例如:
synchronized
?關鍵字、數據庫行鎖。
-
-
樂觀鎖:
-
“先操作再校驗”,適合高并發讀場景,沖突少時性能極高。
-
例如:
CAS
?操作、StampedLock
?的樂觀讀模式。
-
選擇建議
-
用?悲觀鎖?當:
-
寫操作頻繁,或沖突概率高(如支付系統)。
-
業務邏輯復雜,需嚴格保證數據一致性。
-
-
用?樂觀鎖?當:
-
讀多寫少,且沖突概率低(如商品瀏覽統計)。
-
追求高吞吐量,能容忍重試或短暫不一致。
-
2.重量級鎖vs輕量級鎖?
重量級鎖:應對悲觀的場景,此時就要付出更多的代價,更低效。
輕量級鎖:應對樂觀的場景,此時付出的代價更小,更高效。
特性 | 重量級鎖 | 輕量級鎖 |
---|---|---|
鎖級別 | 最高級別的鎖(如操作系統級互斥鎖) | JVM 層面的優化鎖(基于 CAS 自旋) |
實現原理 | 通過操作系統內核的?互斥量(Mutex)?實現,涉及用戶態/內核態切換。 | 通過?CAS(Compare-And-Swap)?和?線程棧中的鎖記錄(Lock Record)?實現。 |
阻塞方式 | 未獲取鎖的線程會?直接掛起(進入內核態等待隊列),由操作系統調度喚醒。 | 先通過?自旋(忙等待)?嘗試獲取鎖,失敗后才升級為重量級鎖。 |
性能開銷 | 高(上下文切換、內核態切換、CPU 調度延遲)。 | 低(無系統調用,僅在用戶態自旋)。 |
適用場景 | 高競爭、長臨界區(如長時間持有鎖的復雜操作)。 | 低競爭、短臨界區(如快速計算的簡單操作)。 |
鎖升級 | 是鎖膨脹的最終狀態(輕量級鎖/偏向鎖失敗后升級)。 | 是偏向鎖失敗后的中間狀態,可能進一步升級為重量級鎖。 |
典型例子 | synchronized ?在競爭激烈時的最終狀態。 | synchronized ?在低競爭時的優化狀態。 |
優點 | 嚴格保證線程安全,適合高并發沖突場景。 | 減少內核態開銷,提高短任務性能。 |
缺點 | 吞吐量低,響應延遲高。 | 自旋浪費 CPU(長時間自旋可能反而降低性能)。 |
選擇建議
-
用?輕量級鎖:
-
代碼塊執行時間極短(如計數器遞增)。
-
線程競爭概率低(如局部變量同步)。
-
-
用?重量級鎖:
-
臨界區執行時間長(如數據庫事務)。
-
競爭激烈(如全局緩存更新)。
-
3.掛起等待鎖vs自旋鎖
掛起等待鎖:(重量級鎖的經典實現)一旦發生競爭,獲取不到鎖,就進入阻塞狀態。
自旋鎖:(輕量級鎖的經典實現)一旦發生競爭,獲取不到鎖,就循環請求,也就是“忙等”。發生在“樂觀”情況下,鎖競爭很小,即使“忙等”,很快也能獲取鎖,等待的時間也不會很長?,消耗的資源也就不會很多。
特性 | 掛起等待鎖(阻塞鎖) | 自旋鎖 |
---|---|---|
實現原理 | 獲取不到鎖時,線程進入阻塞狀態(WAITING),釋放CPU資源 | 獲取不到鎖時,線程循環忙等待(自旋),不釋放CPU |
響應速度 | 較慢(需要線程切換和喚醒) | 極快(無上下文切換) |
CPU占用 | 低(線程掛起后不消耗CPU) | 高(持續占用CPU自旋) |
適用場景 | 鎖持有時間較長(如I/O操作、復雜計算) | 鎖持有時間極短(如CAS操作、簡單計算) |
公平性 | 通常支持公平鎖(按申請順序獲取鎖) | 通常是非公平鎖(競爭機制) |
鎖升級 | 可能涉及內核態切換(如synchronized 重量級鎖) | 完全在用戶態運行(無系統調用) |
典型實現 | Java的synchronized (競爭激烈時)、ReentrantLock | Java的AtomicInteger 、SpinLock (自定義實現) |
優點 | 節省CPU資源,適合高競爭場景 | 無上下文切換,延遲極低 |
缺點 | 線程切換開銷大,響應延遲高 | 自旋過久會浪費CPU,可能饑餓 |
死鎖風險 | 可能死鎖(需超時或中斷機制) | 無死鎖(但可能活鎖) |
?關鍵細節說明
-
掛起等待鎖(阻塞鎖)
-
適用場景:鎖競爭激烈或臨界區執行時間較長(如數據庫事務)。
-
優化建議:
-
使用
ReentrantLock
替代synchronized
(支持超時和中斷)。 -
設置合理的鎖超時時間(避免死鎖)。
-
-
-
自旋鎖
-
適用場景:鎖持有時間極短(如計數器遞增)。
-
優化建議:
-
限制自旋次數(如JVM的
-XX:PreBlockSpin
參數)。 -
升級為自適應自旋鎖(根據歷史自旋時間動態調整)。
-
-
選擇建議
-
優先用自旋鎖:
-
臨界區代碼執行時間?< CPU上下文切換時間(通常約1μs)。
-
例如:
AtomicInteger
的CAS操作。
-
-
優先用掛起等待鎖:
-
臨界區代碼執行時間?> 自旋消耗的CPU時間。
-
例如:同步訪問數據庫連接池。
-
4.普通互斥鎖vs讀寫鎖
普通互斥鎖:鎖之間完全互斥,同一時間只能有一個線程持有鎖,分為‘加鎖’和‘解鎖’兩個過程。synchronized就是經典的普通互斥鎖。
讀寫鎖:應對某些讀多寫少的情況,分為“讀鎖”和“寫鎖”兩種,其中“讀鎖”和“讀鎖”之間不互斥,支持了多個線程同時讀取的情況,減少了鎖沖突的情況。
? ? ? ?讀寫鎖互斥規則詳解
-
讀鎖 vs 讀鎖
-
不互斥:多個線程可同時持有讀鎖(共享)。
-
示例:100個線程可同時讀取緩存數據。
-
-
讀鎖 vs 寫鎖
-
完全互斥:
-
已有讀鎖時,寫鎖請求被阻塞(直到所有讀鎖釋放)。
-
已有寫鎖時,讀鎖請求被阻塞(直到寫鎖釋放)。
-
-
-
寫鎖 vs 寫鎖
-
完全互斥:同一時刻只允許一個寫鎖存在。
-
? ? ? ?普通互斥鎖 vs 讀寫鎖對比
特性 | 普通互斥鎖(Mutex Lock) | 讀寫鎖(Read-Write Lock) |
---|---|---|
鎖模式 | 排他鎖(Exclusive Lock) | 讀鎖(共享鎖) + 寫鎖(排他鎖) |
并發性 | 完全互斥,同一時刻只有一個線程能持有鎖 | 讀鎖可共享(允許多線程同時讀),寫鎖互斥 |
適用場景 | 寫操作頻繁或讀寫操作時間相近 | 讀多寫少(如緩存、配置讀取) |
性能 | 高競爭下性能差 | 讀并發高,寫操作會阻塞所有讀/寫 |
公平性 | 支持公平/非公平(如ReentrantLock ) | 通常支持公平/非公平(如ReentrantReadWriteLock ) |
鎖降級 | 不支持 | 支持(持有寫鎖的線程可以獲取讀鎖,再釋放寫鎖) |
鎖升級 | 不支持 | 不支持(讀鎖不能直接升級為寫鎖,易死鎖) |
實現復雜度 | 簡單 | 較復雜(需維護讀/寫狀態) |
典型實現 | Java:?synchronized 、ReentrantLock | Java:?ReentrantReadWriteLock |
優點 | 實現簡單,嚴格保證一致性 | 讀操作高并發,適合讀多寫少場景 |
缺點 | 讀寫均互斥,并發性差 | 寫操作饑餓風險(長期有讀鎖時寫鎖無法獲取) |
5.可重入鎖vs不可重入鎖?
可重入鎖:允許同一個線程多次獲取一把鎖。
不可重入鎖:不允許同一個線程多次獲取同一把鎖。
特性 | 可重入鎖 | 不可重入鎖 |
---|---|---|
遞歸調用支持 | ? 同一線程可重復獲取同一把鎖(計數器遞增) | ? 同一線程重復獲取會死鎖 |
死鎖風險 | 低(允許嵌套加鎖) | 高(遞歸調用或嵌套加鎖直接死鎖) |
實現復雜度 | 較高(需維護持有線程和重入次數) | 簡單(僅檢查鎖是否被占用) |
典型應用場景 | 遞歸函數、同步方法調用其他同步方法 | 簡單臨界區保護(無嵌套調用場景) |
性能開銷 | 略高(需維護重入狀態) | 較低(無狀態跟蹤) |
鎖釋放要求 | 必須釋放與獲取次數匹配(如加鎖3次需解鎖3次) | 只需釋放1次 |
公平性支持 | 通常支持(如ReentrantLock(true) ) | 通常不支持 |
中斷響應 | 支持(lockInterruptibly() ) | 一般不支持 |
實現示例 | Java:?synchronized 、ReentrantLock C++:? std::recursive_mutex | Java: 自定義簡單鎖 C:? pthread_mutex (默認不可重入) |
適用性 | 通用場景(尤其是復雜同步邏輯) | 極簡場景(無嵌套/遞歸) |
選擇建議
-
用可重入鎖:
-
存在方法嵌套/遞歸調用同步代碼。
-
使用類庫提供的鎖(如Java的
synchronized
)。
-
-
用不可重入鎖:
-
絕對無嵌套的簡單臨界區(性能敏感場景)。
-
需要極輕量級同步原語(如自定義自旋鎖)。
-
6.公平鎖vs不公平鎖
公平鎖:線程采用“先來后到”的思想來依次獲取鎖。
不公平鎖:線程采用“概率相等”的思想來獲取鎖。
1)?公平鎖和不公平鎖的對比
特性 | 公平鎖 (Fair Lock) | 非公平鎖 (Non-Fair Lock) |
---|---|---|
獲取鎖順序 | 嚴格按照線程請求順序(先到先得) | 允許插隊(新線程可能直接搶到鎖) |
吞吐量 | 較低(線程切換頻繁) | 較高(減少線程切換) |
線程饑餓 | 不會發生(保證每個線程有機會執行) | 可能發生(某些線程長期搶不到鎖) |
實現復雜度 | 較高(需維護等待隊列) | 較低(直接競爭) |
適用場景 | 要求嚴格公平性(如訂單處理、金融交易) | 高并發場景,追求性能(如緩存、計數器) |
鎖獲取策略 | 檢查是否有等待更久的線程,有則排隊 | 直接嘗試獲取鎖,失敗才進入隊列 |
典型實現 | ReentrantLock(true) | ReentrantLock(false) (默認)、synchronized |
響應時間 | 較穩定(按序執行) | 波動較大(可能某些線程更快) |
CPU利用率 | 較低(線程頻繁掛起/喚醒) | 較高(減少上下文切換) |
鎖競爭激烈時 | 所有線程按序執行,但吞吐量下降 | 可能某些線程一直搶到鎖,其他線程等待更久 |
2)選擇建議
場景 | 推薦鎖類型 | 原因 |
---|---|---|
需要嚴格順序(如交易系統) | 公平鎖 | 避免某些請求被無限延遲 |
高并發、低延遲(如緩存) | 非公平鎖 | 減少線程切換,提高吞吐量 |
線程執行時間差異大 | 公平鎖 | 防止短任務“餓死”長任務 |
鎖競爭不激烈 | 非公平鎖 | 插隊概率低,性能接近公平鎖但開銷更小 |
7.鎖升級
鎖升級是多線程并發控制中的一種優化策略,指JVM根據競爭情況動態調整鎖的級別,從低開銷鎖逐步升級為高開銷鎖的過程。
無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
鎖級別 | 升級時機 | 特點 | 適用場景 |
---|---|---|---|
無鎖 | 無 | 對象未被任何線程鎖定 | 新建對象 |
偏向鎖 | 代碼進入synchronized代碼塊 =>偏向鎖 | 僅記錄線程ID,無實際同步,只是一個標記,不是鎖 | 單線程訪問 |
輕量級鎖 | 其他線程嘗試獲取這個鎖 =>輕量級鎖 | CAS自旋嘗試獲取鎖 | 低競爭多線程 |
重量級鎖 | JVM發現,當前競爭鎖的情況非常激烈 =>重量級鎖 | 操作系統互斥量實現 | 高競爭場景 |
總的來說,鎖升級是懶漢模式思想的一種體現,旨在減少開銷。鎖升級只升不降,不可以再轉化回去。
8.鎖優化
鎖優化是提升多線程程序性能的關鍵手段,JIT編譯器通過逃逸分析移除不必要的鎖。旨在減少鎖的開銷。
9.鎖的粗化
鎖粗化是JVM針對同步代碼的一種重要優化技術,它通過合并相鄰的同步塊來減少不必要的鎖獲取/釋放操作,從而提升程序執行效率。
粒度級別 | 含義 | |||
---|---|---|---|---|
粗粒度 | 在一把鎖之間代碼更多 | |||
細粒度 | 在一把鎖之間代碼更少 |
?鎖的粗化實際上就是通過將多把鎖合并為一把鎖來減少頻繁上鎖、解鎖的開銷,使得一把鎖之間的代碼變得更多。
10.synchronized
鎖的類型 | synchronized |
---|---|
悲觀鎖還是樂觀鎖? | 自適應 |
重量級鎖還是輕量級鎖? | 自適應 |
掛起等待鎖還是自旋鎖? | 自適應 |
普通互斥鎖還是讀寫鎖? | 普通互斥鎖 |
可重入鎖還是不可重入鎖? | 可重入鎖 |
公平鎖還是不公平鎖? | 公平鎖 |
11.reentrantlock和synchronized的對比
ReentrantLock 是 Java 并發包(java.util.concurrent.locks)中提供的可重入互斥鎖實現,它比傳統的 synchronized 關鍵字提供更靈活的鎖操作。?
以下是reentrantlock和synchronized的五點區別:
區別 | reentrantlock | synchronized |
---|---|---|
實現方式 | Java標準庫中的類,由JDK實現 | 關鍵字,由JVM實現,JVM基于C++實現 |
加鎖方法 | 通過lock()和unlock()方法加鎖和解鎖,有可能因為忘記解鎖而觸發線程安全問題 | 通過代碼塊控制加鎖、解鎖 |
trylock()方法 | 具備trylock()方法,嘗試獲取鎖而不會無限期阻塞 | 不具備 |
公平鎖與非公平鎖 | 默認是非公平鎖,但是也可以實現公平鎖 | 非公平鎖 |
等待通知機制 | 等待通知機制是condition類,比synchronized的wait、notify功能更為強大 | wait、notify |