隨著高併發場景普及,JVM 鎖優化機制成為 Java 開發者必備知識。本文系統梳理偏向鎖、輕量級鎖、重量級鎖、自旋鎖四大鎖類型的原理、升級流程與適用場景,並附帶 JMH 風格效能測試程式碼。
JDK 1.6 引入鎖升級機制,鎖狀態隨併發程度動態轉換:
無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
核心目標:無競爭時零同步開銷,低競爭時用戶態自旋,高競爭時OS層面阻塞。
「一個線程反覆獲得同一把鎖,無其他線程競爭時,省去 CAS 操作」。首次獲得鎖時在對象頭 Mark Word 記錄線程 ID,後續同線程進入同步塊僅需檢查 ID 一致即可,無需 CAS。
-XX:+UseBiasedLocking # 預設開啟(JDK 1.6+)
-XX:-UseBiasedLocking # 禁用(高併發場景建議關閉)
單線程循環 1000 萬次操作 Vector(同步容器):
| 配置 | 耗時(ms) | 說明 |
|---|---|---|
| 開啟偏向鎖 | ~120 | 同步開銷近似 ArrayList |
| 關閉偏向鎖 | ~350 | 每次需 CAS 競爭鎖 |
測試程式碼:
public class BiasedLockTest {
static List list = new Vector<>();
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10_000_000; i++) list.add(i);
System.out.println(System.currentTimeMillis() - start);
}
}
偏向鎖失敗(有其他線程嘗試獲取鎖)時升級。
BasicObjectLock(包含 BasicLock + 對象指標)BasicLock 的指標(標記位 00)BasicLock.displaced_header優勢:無 OS 內核切換,用戶態 CAS 完成加解鎖,適合「短時間持鎖、低併發」場景。
輕量級鎖自旋失敗(JDK 1.6 預設自旋 10 次),或鎖被其他線程長時間持有。
ObjectMonitor(C++ 實現,基於 OS Mutex)_WaitSet 或 _EntryList 排隊,OS 掛起/喚醒代價:用戶態↔內核態切換、線程上下文切換,延遲微秒級以上。
線程不立即阻塞,執行空循環(PAUSE 指令)等待鎖釋放。若自旋期間鎖釋放,直接獲得,避免阻塞開銷。
-XX:+UseSpinning 開啟,-XX:PreBlockSpin=10 設定次數| 場景 | 自旋效果 |
|---|---|
| 鎖持有時間極短、併發低 | ✅ 顯著降低阻塞開銷 |
| 鎖持有時間長、併發高 | ❌ 白白消耗 CPU,最終仍需阻塞 |
單線程/極低併發 → 偏向鎖(預設開啟)
↓ 有競爭
低併發、短持鎖 → 輕量級鎖(CAS)
↓ 自旋失敗/長持鎖
高併發/長持鎖 → 重量級鎖(OS Mutex)
實戰建議:縮小同步塊範圍、降低鎖粒度、使用無鎖資料結構(CAS、ThreadLocal) 才是根本優化之道。