鎖升級是 Java 中?synchronized 鎖?的核心優化機制(基于 JVM 的?對象頭 Mark Word
?實現),指鎖的狀態從?無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖?逐步升級的過程。其目的是通過 “按需升級”,在不同并發場景下選擇最優的鎖實現,平衡性能與線程安全。
一、鎖的四種狀態
在講解升級過程前,需先明確 synchronized 鎖的四種狀態(按開銷從小到大排序):
鎖狀態 | 核心特點 | 適用場景 |
---|---|---|
無鎖 | 無鎖競爭,對象未被任何線程鎖定 | 單線程訪問 |
偏向鎖 | 僅記錄第一個獲取鎖的線程 ID,后續該線程可直接重入,無需競爭 | 單線程重復獲取鎖(無并發) |
輕量級鎖 | 多線程交替獲取鎖,通過?CAS 操作?競爭鎖,避免內核態切換 | 低并發(線程交替執行) |
重量級鎖 | 多線程同時競爭鎖,依賴操作系統?互斥量(Mutex),會阻塞線程 | 高并發(線程頻繁競爭) |
二、鎖升級的完整過程
鎖升級的觸發條件是 “并發競爭加劇”,JVM 會根據線程競爭情況,自動將鎖從低開銷狀態升級到高開銷狀態,且升級過程是?單向的(不可逆)(如偏向鎖升級為輕量級鎖后,不會再降級為偏向鎖)。
1. 第一步:無鎖 → 偏向鎖
- 觸發場景:單線程首次獲取 synchronized 鎖時,JVM 為了減少鎖開銷,會將鎖初始化為 “偏向鎖”。
- 核心操作:
- 線程 A 嘗試獲取鎖時,檢查對象頭的?
Mark Word
(存儲對象鎖狀態的字段),發現當前是 “無鎖” 狀態。 - 通過?CAS 操作,將?
Mark Word
?中的 “鎖狀態” 改為 “偏向鎖”,并記錄線程 A 的 ID(threadId
)和 “偏向時間戳”。 - 后續線程 A 再次進入 synchronized 代碼塊時,只需對比?
Mark Word
?中的線程 ID 是否為自己:- 是:直接重入鎖,無需任何 CAS 或阻塞操作(開銷極低)。
- 否:觸發偏向鎖撤銷,進入下一步升級。
- 線程 A 嘗試獲取鎖時,檢查對象頭的?
2. 第二步:偏向鎖 → 輕量級鎖
- 觸發場景:當有?第二個線程(線程 B)嘗試獲取同一把鎖?時,偏向鎖的 “單線程假設” 被打破,JVM 會撤銷偏向鎖,升級為 “輕量級鎖”。
- 核心操作:
- 線程 B 嘗試獲取鎖時,發現?
Mark Word
?記錄的是線程 A 的 ID(偏向鎖狀態),且線程 A 可能仍在執行(或已退出但未清理偏向鎖)。 - JVM 首先會?暫停線程 A,檢查線程 A 的狀態:
- 若線程 A 已退出:將?
Mark Word
?重置為 “無鎖”,線程 B 重新嘗試 CAS 獲取輕量級鎖。 - 若線程 A 仍在執行:撤銷偏向鎖(將?
Mark Word
?中的偏向狀態清除),進入輕量級鎖競爭。
- 若線程 A 已退出:將?
- 輕量級鎖的競爭邏輯:
- 線程 A 和線程 B 會分別在自己的?棧幀?中創建一個 “鎖記錄(Lock Record)”,存儲對象頭?
Mark Word
?的副本(稱為?Displaced Mark Word
)。 - 線程通過?CAS 操作,嘗試將對象頭的?
Mark Word
?指向自己的 “鎖記錄地址”:- 成功:獲取輕量級鎖,執行同步代碼。
- 失敗:判斷當前鎖是否仍為輕量級鎖(未升級),若仍是,則自旋重試 CAS(避免阻塞);若自旋次數超過閾值(默認 10 次,或自適應自旋),則進入下一步升級。
- 線程 A 和線程 B 會分別在自己的?棧幀?中創建一個 “鎖記錄(Lock Record)”,存儲對象頭?
- 線程 B 嘗試獲取鎖時,發現?
3. 第三步:輕量級鎖 → 重量級鎖
- 觸發場景:當?多個線程同時競爭鎖(如線程 A、B、C 同時嘗試獲取鎖),或輕量級鎖的?CAS 自旋重試失敗?時,輕量級鎖的 “交替執行假設” 被打破,JVM 會將鎖升級為 “重量級鎖”。
- 核心操作:
- 線程 C 嘗試 CAS 獲取輕量級鎖時,發現對象頭的?
Mark Word
?已指向線程 A 的鎖記錄(線程 A 持有輕量級鎖),且自旋重試多次后仍未成功。 - JVM 會將輕量級鎖的 “鎖狀態” 改為 “重量級鎖”,并將對象頭的?
Mark Word
?指向?操作系統的互斥量(Mutex)?地址。 - 此時未獲取到鎖的線程(如 B、C)會?放棄 CAS 自旋,轉而調用操作系統的?
park()
?方法,進入?阻塞狀態(內核態切換,開銷高)。 - 當持有鎖的線程 A 釋放鎖時,會通過操作系統的?
unpark()
?方法,喚醒阻塞隊列中的一個線程(如 B),線程 B 重新競爭重量級鎖。
- 線程 C 嘗試 CAS 獲取輕量級鎖時,發現對象頭的?
三、關鍵細節:對象頭 Mark Word 的角色
鎖升級的本質是?對象頭 Mark Word 的狀態變化,不同鎖狀態下,Mark Word
?的存儲內容不同(以 64 位 JVM 為例):
鎖狀態 | Mark Word 存儲內容(簡化) | 鎖狀態標識位 |
---|---|---|
無鎖 | 對象哈希碼(hashCode) + 無鎖標識 | 01 |
偏向鎖 | 偏向線程 ID + 偏向時間戳 + 偏向鎖標識 | 01(特殊標記) |
輕量級鎖 | 指向線程棧幀中 “鎖記錄” 的指針 + 輕量級鎖標識 | 00 |
重量級鎖 | 指向操作系統 “互斥量” 的指針 + 重量級鎖標識 | 10 |
JVM 通過讀取?Mark Word
?的 “鎖狀態標識位”,即可判斷當前鎖的狀態,進而執行對應的升級邏輯。
四、鎖升級的核心目的
鎖升級的設計思想是 “因地制宜”:
單線程場景:用偏向鎖最小化鎖開銷(幾乎無成本)。
低并發場景:用輕量級鎖的 CAS 操作避免線程阻塞(用戶態操作,開銷低)。
高并發場景:用重量級鎖的互斥量保證線程安全(雖開銷高,但能穩定處理競爭)。
通過這種 “按需升級” 的機制,synchronized 鎖在不同并發場景下都能兼顧性能與安全性,避免了 “一刀切” 的鎖開銷問題(如早期 synchronized 直接使用重量級鎖,單線程場景下也有高開銷)。