目錄
一、核心機制深度解析
1. 對象頭(Object Header)與Mark Word的奧秘
2. Monitor:同步的實質
二、鎖升級的全過程與底層操作
1. 無鎖 -> 偏向鎖
2. 偏向鎖 -> 輕量級鎖
3. 輕量級鎖 -> 重量級鎖
三、高級話題與實戰調優
1. 鎖的優劣對比
2. 鎖降級
? ? ? ? 3. synchronized的性能問題
作為Java并發編程的基石,synchronized
?關鍵字的重要性不言而喻。它不僅僅是一個關鍵字,更是一套完整的線程同步解決方案,其背后蘊含著Java虛擬機精湛的設計哲學。本文將帶領你從字節碼層面到操作系統內核,全方位剖析?synchronized
?的實現原理、優化手段與實戰技巧。
一、核心機制深度解析
synchronized
?的實現建立在兩個核心概念之上:對象頭(Object Header)?和?Monitor(監視器)。理解這兩者是掌握?synchronized
?的關鍵。
1. 對象頭(Object Header)與Mark Word的奧秘
每個Java對象在堆內存中的存儲布局都包含以下部分:
-
對象頭 (Object Header):包含兩類信息:
-
Mark Word:這是實現鎖的核心。在64位JVM中,它通常占64位(8字節),是一個動態變化的數據結構,其內容會根據鎖的狀態而發生改變。它用于存儲對象的哈希碼(hashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖信息、偏向線程ID等。
-
Klass Pointer:指向對象的類元數據(Class對象)的指針,JVM通過它來確定對象屬于哪個類。在64位JVM中,默認開啟指針壓縮(-XX:+UseCompressedOops),此指針被壓縮為32位。
-
-
實例數據 (Instance Data):對象真正存儲的有效信息,即程序代碼中定義的各種字段內容。
-
對齊填充 (Padding):起占位符作用,HotSpot VM要求對象起始地址必須是8字節的整數倍,因此需要對對象大小進行對齊填充。
Mark Word在不同鎖狀態下的結構是其精髓所在,如下圖所示:
(此處應有一張Mark Word內存布局圖,展示無鎖、偏向鎖、輕量級鎖、重量級鎖、GC標記狀態下的位分布)
鎖狀態 | 偏向鎖標志 (biased_lock) | 鎖標志位 (lock) | 存儲內容 |
---|---|---|---|
無鎖 | 0 | 01 | 對象的哈希碼(hashCode)、對象分代年齡 |
偏向鎖 | 1 | 01 | 持有偏向鎖的線程ID、epoch、對象分代年齡 |
輕量級鎖 | - | 00 | 指向棧中鎖記錄(Lock Record)的指針 |
重量級鎖 | - | 10 | 指向操作系統互斥量(Mutex)和等待隊列的指針 |
GC標記 | - | 11 | 空(表示該對象正被垃圾回收) |
2. Monitor:同步的實質
每個Java對象都可以關聯一個Monitor(監視器鎖)。在HotSpot虛擬機中,Monitor是由?ObjectMonitor
?類(C++實現)實現的,其主要數據結構包括:
-
_owner
:指向當前持有該Monitor的線程。 -
_EntryList
:存儲所有阻塞等待獲取該Monitor的線程。 -
_WaitSet
:存儲調用了?Object.wait()
?方法而進入等待狀態的線程。
當線程執行到?synchronized
?保護的代碼塊時:
-
執行?
monitorenter
?指令,嘗試通過CAS操作將Monitor的?_owner
?字段設置為當前線程。 -
如果成功,該線程即持有Monitor,進入臨界區執行代碼。
-
如果失敗,說明Monitor已被其他線程持有,當前線程會進入?
_EntryList
?隊列中阻塞等待。 -
持有Monitor的線程執行完同步代碼后,會執行?
monitorexit
?指令,將?_owner
?置為?null
?并喚醒?_EntryList
?中的線程,它們將重新競爭鎖。
二、鎖升級的全過程與底層操作
鎖升級是?synchronized
?性能優化的核心,其目的是在無競爭或低競爭情況下,避免使用重量級鎖帶來的高昂開銷。
1. 無鎖 -> 偏向鎖
-
觸發條件:第一個線程訪問同步塊。
-
底層操作:JVM使用CAS操作,將當前線程ID寫入對象頭的Mark Word中,并設置偏向鎖標志。如果成功,則線程成功獲取偏向鎖。
-
優勢:此后該線程再進入同步塊時,只需檢查Mark Word中的線程ID是否為自己,是則直接執行,無需任何同步操作,開銷極小。
2. 偏向鎖 -> 輕量級鎖
-
觸發條件:有另一個線程來競爭鎖(偏向鎖發生撤銷)。
-
底層操作:
-
首先,JVM會暫停擁有偏向鎖的線程。
-
然后,在該線程的棧幀中創建一個鎖記錄(Lock Record)?空間。
-
將對象當前的Mark Word復制到該線程的鎖記錄中(稱為Displaced Mark Word)。
-
線程使用CAS操作嘗試將對象頭的Mark Word替換為指向其鎖記錄的指針。
-
如果成功,當前線程獲得輕量級鎖;如果失敗,表示存在競爭,進而升級為重量級鎖。
-
-
優勢:在沒有真正競爭的情況下,使用CAS這種用戶態操作避免了操作系統內核態切換的開銷。
3. 輕量級鎖 -> 重量級鎖
-
觸發條件:輕量級鎖的CAS操作失敗(自旋后仍無法獲取鎖)。
-
底層操作:
-
當前線程會先自旋一小段時間(自適應自旋),嘗試獲取鎖,以避免直接升級帶來的開銷。
-
如果自旋后仍無法獲取,鎖正式升級為重量級鎖。
-
JVM會向操作系統申請一個互斥量(Mutex),并將對象頭的Mark Word更新為指向該互斥量的指針。
-
未能獲取鎖的線程會被掛起(park),進入?
_EntryList
?隊列,等待操作系統的調度喚醒。
-
-
開銷:線程的掛起和喚醒涉及用戶態到內核態的切換,上下文切換成本非常高。
三、高級話題與實戰調優
1. 鎖的優劣對比
鎖類型 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖解鎖無額外開銷 | 鎖撤銷有額外開銷 | 單線程訪問同步塊 |
輕量級鎖 | 競爭線程不阻塞,程序響應快 | 長時間自旋會消耗CPU | 追求響應時間,同步塊執行快 |
重量級鎖 | 競爭線程不自旋,不消耗CPU | 線程阻塞,響應慢 | 追求吞吐量,同步塊執行時間長 |
2. 鎖降級
鎖降級確實存在,但其場景非常有限,主要發生在?GC的STW階段。為了減少GC停頓時間,JVM會嘗試進行鎖降級。但在正常的用戶代碼執行路徑中,鎖升級是不可逆的,這是為了避免在重量級鎖和輕量級鎖之間反復切換帶來的巨大性能損耗。
3. 性能調優最佳實踐
-
減少鎖粒度:縮小同步代碼塊的范圍,最經典的例子是?
ConcurrentHashMap
?使用分段鎖。 -
減少鎖持有時間:避免在同步塊內執行耗時的I/O操作、網絡請求或復雜計算。
-
讀寫分離:使用?
ReadWriteLock
?替代獨占鎖,允許讀讀并發,提高讀多寫少場景的性能。 -
JVM參數調優:
-
-XX:-UseBiasedLocking
:明確知道會有高競爭時,可禁用偏向鎖。 -
-XX:BiasedLockingStartupDelay=0
:取消偏向鎖延遲(默認4s),適用于啟動后立即高并發的應用。 -
-XX:+UseSpinning
?/?-XX:PreBlockSpin
:控制輕量級鎖的自旋策略(JDK6之后是自適應自旋,通常無需手動調整)。
-
? ? ? ? 3. synchronized的性能問題
- 鎖粒度太大:同步范圍覆蓋過多無關代碼,導致線程競爭加劇。
- 鎖持有時間過長:同步塊中包含耗時操作。
- 鎖競爭激烈:多個線程頻繁爭搶同一把鎖,導致上下文切換頻繁。
- 鎖升級頻繁:大量競爭導致鎖從偏向鎖升級到重量級鎖。
- 死鎖:線程互相等待對方釋放鎖,導致系統停滯。