Java 中的 synchronized
關鍵字是保證線程安全的基本機制,隨著 JVM 的發展,它經歷了多次優化以提高性能。
1. 鎖升級機制(鎖膨脹)
JDK 1.6 引入了偏向鎖→輕量級鎖→重量級鎖的升級機制,避免了一開始就使用重量級鎖:
1.1 偏向鎖(Biased Locking)
- 優化場景:只有一個線程訪問同步塊
- 實現:在對象頭記錄偏向線程ID
- 優勢:幾乎無同步開銷
- 觸發升級:當有其他線程嘗試獲取鎖時
1.2 輕量級鎖(Thin Lock)
- 優化場景:多線程交替訪問但無競爭
- 實現:通過CAS操作和棧幀中的Lock Record實現
- 優勢:避免操作系統層面的線程阻塞
- 觸發升級:當自旋獲取鎖失敗(默認自旋10次)
1.3 重量級鎖(Heavyweight Lock)
- 場景:真正的高競爭情況
- 實現:通過操作系統的互斥量(mutex)實現
- 特點:線程會進入阻塞狀態
2. 自適應自旋鎖(Adaptive Spinning)
- 自旋次數不再固定,而是根據:
- 前一次在該鎖上的自旋成功情況
- 鎖擁有者的狀態
- 如果上次自旋成功,則增加自旋次數
- 如果很少成功,則可能直接跳過自旋
3. 鎖消除(Lock Elimination)
- 優化場景:JIT 編譯器通過逃逸分析確定對象不會逃逸當前線程
- 效果:完全移除不必要的同步操作
- 示例:
public void method() {Object lock = new Object(); // 局部對象,不會逃逸synchronized(lock) { // 會被優化掉// do something} }
4. 鎖粗化(Lock Coarsening)
- 優化場景:相鄰的同步塊使用同一個鎖
- 效果:合并多個同步塊為一個,減少鎖的獲取/釋放次數
- 示例:
// 優化前 synchronized(lock) { do1(); } synchronized(lock) { do2(); }// 優化后 synchronized(lock) { do1();do2(); }
5. 其他優化
5.1 偏向鎖延遲啟用
- 默認情況下,JVM 在啟動后4秒才啟用偏向鎖(通過
BiasedLockingStartupDelay
參數配置) - 避免啟動階段大量競爭導致的偏向鎖撤銷開銷
5.2 批量重偏向(Bulk Rebias)
- 當一類鎖對象被多個線程交替使用,但未真正競爭時
- JVM 會批量重置這些對象的偏向鎖,而不是逐個撤銷
5.3 批量撤銷(Bulk Revoke)
- 當一類鎖對象的偏向模式不再有效時
- JVM 會一次性撤銷所有該類實例的偏向鎖
性能對比(JDK 1.6+ vs 早期版本)
場景 | 早期版本 | JDK 1.6+ 優化后 |
---|---|---|
單線程訪問 | 重量級鎖開銷 | 偏向鎖零開銷 |
低競爭交替訪問 | 重量級鎖開銷 | 輕量級鎖CAS操作 |
短時間高競爭 | 線程立即阻塞 | 自旋嘗試獲取 |
長時間高競爭 | 線程阻塞 | 最終仍會阻塞 |
最佳實踐
- 減少同步范圍:只在必要代碼塊加鎖
- 降低鎖粒度:使用多個細粒度鎖而非一個大鎖
- 避免鎖嵌套:容易導致死鎖
- 考慮替代方案:在適當場景使用
java.util.concurrent
包中的并發工具
這些優化使得 synchronized
在大多數場景下的性能已經接近或超過顯式鎖(如 ReentrantLock
),同時保持了更好的安全性和易用性。
你想要的面試技術資料我全都有:https://pan.q刪掉漢子uark.cn/s/aa7f2473c65b