1. 程式人生 > 實用技巧 >synchronized 鎖升級/鎖膨脹

synchronized 鎖升級/鎖膨脹

偏向鎖#

偏向第一個拿到鎖的執行緒。

即第一個拿到鎖的執行緒,鎖會在物件頭 Mark Word 中通過 CAS 記錄該執行緒 ID,該執行緒以後每次拿鎖時都不需要進行 CAS(指輕量級鎖)。

如果該執行緒正在執行同步程式碼塊時有其他執行緒在競爭(指其他執行緒嘗試 CAS 讓 Mark Word 設定自己的執行緒 ID),會被升級為輕量級鎖。

如果其他執行緒發現 Mark Word 裡記的不是自己,且發現原持有偏向鎖的執行緒已經執行完同步程式碼塊,會嘗試 CAS 把 Mark Word 中的改為自己的執行緒 ID。

輕量級鎖#

輕量級鎖就是通過 CAS 進行加鎖的。

JVM 會給執行緒的棧幀

中建立一個叫鎖記錄 Lock Record 的空間,把物件頭 Mark Word 複製到該空間裡(Displaced Mark Word),並通過 CAS 嘗試把原物件頭 Mark Word 中鎖記錄指標指向該鎖記錄。如果成功,表示執行緒拿到了鎖。如果失敗,則進行自旋(自旋鎖),自旋超過一定次數時升級為重量級鎖,這時該執行緒會被核心掛起。

自旋鎖#

輕量級鎖膨脹為重量級鎖前,執行緒在執行 monitorenter 指令進入等待佇列時,會通過自旋去嘗試獲得鎖。

如果自旋超過一定次數時還未拿到鎖,就會進入阻塞狀態,等待核心來排程。此時會發生核心態與使用者態之間的上下文切換,所以會影響效能(引入自旋鎖就是為了減少這個開銷)。

因為後面的執行緒也先進行自旋嘗試獲取鎖,所以這對於已被阻塞的那些執行緒來說,會不公平

重量級鎖#

重量級鎖就是通過核心來操作執行緒。因為頻繁出現核心態與使用者態的切換,會嚴重影響效能。

升級為重量級鎖時會在堆中建立 monitor 物件,並將 Mark Word 指向該 monitor 物件。monitor 中有 cxq(ContentionList),EntryList ,WaitSet,owner:

圖片來自:死磕Synchronized底層實現--重量級鎖

鎖升級的流程圖#

圖片來自:Java Synchronised機制

鎖降級#

Hotspot 在 1.8 開始有了鎖降級。在 STW 期間 JVM 進入安全點時如果發現有閒置的 monitor(重量級鎖物件),會進行鎖降級。

Java鎖優化--JVM鎖降級

為什麼鎖資訊存放在物件頭裡?#

死磕Synchronized底層實現--概論 中:

因為在Java中任意物件都可以用作鎖,因此必定要有一個對映關係,儲存該物件以及其對應的鎖資訊(比如當前哪個執行緒持有鎖,哪些執行緒在等待)。一種很直觀的方法是,用一個全域性map,來儲存這個對映關係,但這樣會有一些問題:需要對map做執行緒安全保障,不同的synchronized之間會相互影響,效能差;另外當同步物件較多時,該map可能會佔用比較多的記憶體。

所以最好的辦法是將這個對映關係儲存在物件頭中,因為物件頭本身也有一些hashcode、GC相關的資料,所以如果能將鎖資訊與這些資訊共存在物件頭中就好了。

也就是說,如果用一個全域性 map 來存物件的鎖資訊,還需要對該 map 做執行緒安全處理,不同的鎖之間會有影響。所以直接存到物件頭。

其他文章#

【死磕Java併發】—–深入分析synchronized的實現原理

【死磕 Java 併發】—– synchronized 的鎖膨脹過程

【轉載】Java中的鎖機制 synchronized & 偏向鎖 & 輕量級鎖 & 重量級鎖 & 各自優缺點及場景 & AtomicReference

死磕Synchronized底層實現--概論