在Java中,synchronized
關鍵字是用于實現線程同步的重要機制,它通過內置鎖(Monitor)確保多個線程對共享資源的安全訪問。
1. synchronized
的基本使用與實現原理
使用方式
- 修飾實例方法:鎖是當前對象實例。
public synchronized void method() { ... }
- 修飾靜態方法:鎖是當前類的Class對象。
public static synchronized void staticMethod() { ... }
- 同步代碼塊:需顯式指定鎖對象(任意對象均可)。
synchronized (lockObject) { ... }
底層實現
- Monitor 機制:每個對象關聯一個Monitor(監視器鎖),通過
monitorenter
和monitorexit
字節碼指令實現加鎖/解鎖。- 線程進入同步塊時,執行
monitorenter
嘗試獲取鎖。 - 退出同步塊時,執行
monitorexit
釋放鎖。
- 線程進入同步塊時,執行
2. 對象頭與鎖狀態標記
每個Java對象在內存中分為三部分:對象頭(Header)、實例數據(Instance Data) 和 對齊填充(Padding)。對象頭中的Mark Word字段記錄了鎖狀態信息。
Mark Word 結構(以64位JVM為例)
鎖狀態 | 存儲內容 |
---|---|
無鎖 | 對象的哈希碼、分代年齡、是否偏向鎖(1 bit) |
偏向鎖 | 偏向線程ID、偏向時間戳、分代年齡、鎖標志位(01) |
輕量級鎖 | 指向線程棧中鎖記錄(Lock Record)的指針,鎖標志位(00) |
重量級鎖 | 指向Monitor對象(重量級鎖)的指針,鎖標志位(10) |
3. 鎖的升級過程
JVM根據線程競爭情況動態調整鎖狀態,以減少性能開銷。鎖升級的路徑為:
無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
(1) 偏向鎖(Biased Locking)
- 適用場景:單線程反復進入同步塊,無實際競爭。
- 核心機制:
- 對象首次被線程訪問時,將線程ID寫入Mark Word,進入偏向模式。
- 后續該線程進入同步塊時,無需執行CAS操作,直接檢查線程ID是否匹配。
- 優勢:消除無競爭時的同步開銷。
- 撤銷條件:
- 其他線程嘗試獲取鎖時,觸發偏向鎖撤銷。
- 需要等待全局安全點(STW),檢查原線程是否存活或已釋放鎖。
(2) 輕量級鎖(Lightweight Locking)
- 適用場景:多線程交替執行同步塊,競爭不激烈。
- 核心機制:
- 線程在棧幀中創建鎖記錄(Lock Record),將對象Mark Word復制到鎖記錄中。
- 通過CAS將Mark Word替換為指向鎖記錄的指針。成功則獲取鎖;失敗則膨脹為重量級鎖。
- 優勢:避免線程阻塞,通過自旋(CAS)減少內核態切換開銷。
- 自旋優化:
- 適應性自旋:JVM根據歷史自旋成功率動態調整自旋次數。
(3) 重量級鎖(Heavyweight Locking)
- 適用場景:多線程高并發競爭。
- 核心機制:
- Monitor對象(C++實現)管理線程競爭,包含
_owner
(持有者)、_EntryList
(阻塞隊列)、_WaitSet
(等待隊列)。 - 未獲取鎖的線程進入
_EntryList
,由操作系統調度(涉及用戶態到內核態切換)。
- Monitor對象(C++實現)管理線程競爭,包含
- 特點:線程阻塞,響應慢但公平。
4. 鎖升級的觸發條件
步驟 | 觸發條件 |
---|---|
無鎖 → 偏向鎖 | 對象首次被線程訪問,JVM啟用偏向鎖(默認開啟,Java 15后需手動開啟)。 |
偏向鎖 → 輕量級鎖 | 其他線程嘗試獲取鎖,導致偏向鎖撤銷。 |
輕量級鎖 → 重量級鎖 | CAS自旋失敗(超過閾值或競爭激烈),觸發鎖膨脹(Inflate)。 |
5. 鎖的不可逆性與性能權衡
- 不可逆性:鎖升級后無法降級,因為降級會增加復雜性和性能損耗。
- 性能權衡:
- 偏向鎖:適合單線程場景,但撤銷成本高(需STW)。
- 輕量級鎖:適合低競爭場景,依賴CAS自旋。
- 重量級鎖:適合高競爭場景,犧牲響應時間保證穩定性。
6. Monitor 的詳細結構
Monitor對象(如ObjectMonitor
)包含以下關鍵字段:
_owner
:當前持有鎖的線程。_recursions
:鎖的重入次數。_EntryList
:等待獲取鎖的線程隊列。_WaitSet
:調用wait()
后進入等待狀態的線程隊列。
7. 實際案例:鎖升級過程
場景:兩個線程交替執行同步塊
- 初始狀態:對象無鎖。
- 線程A進入同步塊:升級為偏向鎖,Mark Word記錄線程A的ID。
- 線程B嘗試進入:觸發偏向鎖撤銷,升級為輕量級鎖,線程B通過CAS競爭。
- 線程B CAS失敗:自旋后仍未成功,升級為重量級鎖,線程B進入
_EntryList
阻塞。
8. 最佳實踐
- 避免過度同步:減少鎖粒度(如使用
ConcurrentHashMap
)。 - 優先使用輕量級工具:如
ReentrantLock
、StampedLock
(需手動管理)。 - 監控鎖競爭:通過JVM參數(
-XX:+PrintFlagsFinal
)或工具(Arthas)分析鎖狀態。