JVM 併發專題:線程安全分級與鎖優化全景解析
線程安全是多線程程序正確性的基石。本文系統梳理 Java 中線程安全的五個等級、三大實現路線,並深入解析 JVM 鎖優化演進:偏向鎖 → 輕量級鎖 → 重量級鎖,以及自適應自旋、鎖消除、鎖粗化等進階優化。
一、線程安全五等級
- 不可變:
final 修飾、無 setter、狀態創建後凍結。絕對線程安全,如 String、Long、Guava ImmutableList。
- 絕對線程安全:無論調用者如何操作,無需額外同步。Java 中極少見,
Vector、Hashtable 看似安全但復合操作仍需外部鎖。
- 相對線程安全:單個方法原子安全,但多方法組合需外部同步。如
ArrayList、HashMap、Collections.synchronizedList。
- 線程兼容:自身無同步,調用者正確加鎖即可安全使用。絕大多數非線程安全類屬此類。
- 線程對立:無論是否加鎖,多線程環境下均無法正確工作。如
Thread.suspend()/resume()(已棄用)。
二、線程安全三大實現路線
1. 互斥同步(悲觀鎖)
- synchronized:JVM 內置鎖,編譯為
monitorenter/monitorexit 字節碼。可重入、阻塞式。
- ReentrantLock:JUC 顯式鎖,提供 可中斷等待、公平鎖、多條件變量 進階功能。
2. 非阻塞同步(樂觀鎖/CAS)
- 核心思想:先嘗試操作 → 檢測衝突 → 衝突則重試/補償。
- JUC 原子類:
AtomicInteger、LongAdder、ConcurrentHashMap 等底層均基於 CAS + volatile。
- 優勢:無線程掛起/喚醒開銷,高併發低衝突場景吞吐遠超鎖。
3. 無同步方案
- 可重入代碼/純函數:無共享狀態,天然線程安全。
- ThreadLocal:線程私有副本,空間換時間,避免共享。
三、JVM 鎖優化演進 (HotSpot)
synchronized 鎖狀態升級路徑:無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖(不可降級)。
1. 偏向鎖
- 場景:無鎖競爭,同一線程反覆獲得鎖。
- 原理:在對象頭 Mark Word 記錄線程 ID,後續進出同步塊只需 CAS 檢查 ID,無需真正加鎖。
- 撤銷:有其他線程嘗試獲取鎖時,觸發 Safepoint 撤銷偏向,升級為輕量級鎖。
2. 輕量級鎖
- 場景:輕度競爭,線程交替執行。
- 原理:線程棧框分配 Lock Record,CAS 將對象頭 Mark Word 指向 LR,成功即獲鎖;失敗則自旋嘗試。
- 膨脹:自旋超過閾值或第三方線程介入,膨脹為重量級鎖。
3. 重量級鎖
- 場景:激烈競爭、長耗時同步塊。
- 原理:基於 OS Mutex,線程阻塞進入內核態,調度開銷大但公平、無自旋浪費 CPU。
4. 進階優化
- 自適應自旋:JVM 記錄上次自旋成功率,動態調整自旋次數。
- 鎖消除:逃逸分析發現對象不逃逸出線程,JIT 編譯時直接移除同步塊。
- 鎖粗化:連續對同一對象加鎖/解鎖,合併為一個大同步塊,減少開銷。
實戰建議
- 優先使用 不可變對象、JUC 並發容器、原子類 避免手寫同步。
- 必須用鎖時:
synchronized 代碼簡潔、JVM 優化充分,優先考慮;需可中斷/公平/多條件時用 ReentrantLock。
- 減小鎖粒度:
ConcurrentHashMap 分段鎖、LongAdder 分散熱點。
- 善用
-XX:+PrintBiasedLockingStatistics、-XX:+PrintLockStatistics 診斷鎖行為。