Java 鎖升級機制詳解
引言
最近有個三年左右的兄弟面試java 被問到這樣一道經典的八股文面試題: 你講講java里面的鎖升級? 他感覺回答的不是很好,然后回去找資料學習了一波,然后下面是他輸出的文章,希望對找工作的其他朋友也有些幫助。
1. 概述
Java 的鎖升級機制是 JVM 在 JDK 1.6 后引入的重要優化策略,目的是在多線程環境下平衡 線程安全 與 性能開銷。通過動態調整鎖的復雜度,JVM 根據競爭強度逐步升級鎖的狀態,避免在低競爭場景下使用高成本的重量級鎖。
2. 鎖類型及特點
| 鎖類型 | 適用場景 | 性能開銷 | 核心機制 |
|---|---|---|---|
| 無鎖(Unlocked) | 無線程競爭 | 極低 | 直接通過 CAS 操作嘗試獲取鎖。 |
| 偏向鎖(Biased Locking) | 單線程重復訪問(無競爭) | 極低 | 對象頭記錄偏向線程 ID,后續同一線程無需競爭,直接獲取鎖。 |
| 輕量級鎖(Lightweight Lock) | 低競爭(多個線程交替訪問) | 中等 | 通過 CAS 自旋嘗試獲取鎖,避免操作系統級別的阻塞。 |
| 重量級鎖(Heavyweight Lock) | 高競爭(長時間阻塞或高并發) | 高 | 依賴操作系統互斥量(Mutex),線程被掛起并排隊等待。 |
3. 鎖升級的過程
鎖升級路徑為:無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖,且 不可逆(只能升級,不能降級)。
3.1 無鎖 → 偏向鎖
- 觸發條件:第一個線程訪問同步代碼塊時。
- 過程:
- JVM 通過 CAS 操作將對象頭的
Mark Word標記為偏向鎖。 - 記錄當前線程 ID 和偏向時間戳。
- 后續同一線程再次訪問時,直接通過比對線程 ID 獲取鎖(無需 CAS 操作)。
- JVM 通過 CAS 操作將對象頭的
3.2 偏向鎖 → 輕量級鎖
- 觸發條件:第二個線程嘗試獲取同一鎖(出現競爭)。
- 過程:
- 偏向鎖失效,JVM 撤銷偏向鎖(可能觸發 STW,Stop-The-World)。
- 線程通過自旋(Spin)和 CAS 操作嘗試獲取鎖。
- 若自旋成功,則升級為輕量級鎖;否則繼續自旋或升級為重量級鎖。
3.3 輕量級鎖 → 重量級鎖
- 觸發條件:
- 自旋次數超過閾值(默認 10 次,可通過
-XX:PreBlockSpin調整)。 - 多個線程同時競爭鎖(如第三個線程加入競爭)。
- 自旋次數超過閾值(默認 10 次,可通過
- 過程:
- JVM 將鎖升級為重量級鎖,對象頭指向監視器(Monitor)。
- 線程進入操作系統內核態的阻塞隊列,等待調度器喚醒。
- 未獲取鎖的線程通過
ObjectMonitor等待喚醒。
4. 鎖升級的優缺點
4.1 優點
- 減少無競爭場景的開銷:偏向鎖和輕量級鎖避免了頻繁的 CAS 和上下文切換。
- 動態適配競爭強度:在低競爭時保持高性能,在高競爭時保證線程安全。
4.2 缺點
- 偏向鎖撤銷開銷:當其他線程競爭時,撤銷偏向鎖會導致 STW,影響性能。
- 重量級鎖的高開銷:在高競爭場景下,頻繁的線程阻塞/喚醒會顯著降低性能。
5. 鎖升級的優化策略
5.1 減少鎖持有時間
- 優化方向:縮短同步代碼塊的執行時間,降低鎖的競爭概率。
- 示例:
// 不推薦:鎖持有時間過長 synchronized (lock) { // 復雜計算或 IO 操作 } // 推薦:僅在關鍵代碼塊加鎖 int result = doSomeComputation(); // 非同步操作 synchronized (lock) { sharedVariable = result; }
5.2 使用分段鎖(Segment Locking)
- 優化方向:將一個大鎖拆分為多個小鎖,減少鎖的競爭范圍。
- 示例:
ConcurrentHashMap使用分段鎖(JDK 8 后改為 CAS + synchronized)。
5.3 禁用偏向鎖
- 適用場景:頻繁切換線程的場景(如高并發服務)。
- JVM 參數:
-XX:-UseBiasedLocking # 禁用偏向鎖
5.4 調整自旋次數
- 適用場景:輕量級鎖的自旋可能因 CPU 空閑而浪費資源。
- JVM 參數:
-XX:PreBlockSpin=5 # 設置自旋次數為 5
6. 代碼示例
public class LockUpgradeExample {
private final Object lock = new Object();
public void performTask() {
synchronized (lock) {
// 同步代碼塊
}
}
public static void main(String[] args) {
LockUpgradeExample example = new LockUpgradeExample();
Thread t1 = new Thread(example::performTask);
Thread t2 = new Thread(example::performTask);
t1.start(); // 初始為偏向鎖(t1)
t2.start(); // 觸發偏向鎖撤銷,升級為輕量級鎖
}
}
7. 關鍵 JVM 參數
| 參數 | 作用 |
|---|---|
-XX:+UseBiasedLocking |
開啟/關閉偏向鎖(默認開啟,Java 15+ 默認關閉)。 |
-XX:BiasedLockingStartupDelay=0 |
立即啟用偏向鎖(避免延遲)。 |
-XX:PreBlockSpin |
設置輕量級鎖自旋次數(默認 10)。 |
-XX:-UseSpinning |
關閉自旋鎖(強制進入重量級鎖)。 |
8. 總結
- 鎖升級是 JVM 自動管理的機制,開發者無需手動干預,但理解其原理有助于優化并發性能。
- 偏向鎖適合單線程場景,輕量級鎖適合低競爭場景,重量級鎖適合高競爭場景。
- 鎖升級不可逆,一旦升級到重量級鎖,后續操作將始終使用重量級鎖。
通過合理設計代碼(如減少鎖粒度、避免過早膨脹到重量級鎖),可以最大化 Java 的并發性能。
浙公網安備 33010602011771號