synchronized 鎖升級/鎖膨脹
偏向鎖#
偏向第一個拿到鎖的執行緒。
即第一個拿到鎖的執行緒,鎖會在物件頭 Mark Word 中通過 CAS 記錄該執行緒 ID,該執行緒以後每次拿鎖時都不需要進行 CAS(指輕量級鎖)。
如果該執行緒正在執行同步程式碼塊時有其他執行緒在競爭(指其他執行緒嘗試 CAS 讓 Mark Word 設定自己的執行緒 ID),會被升級為輕量級鎖。
如果其他執行緒發現 Mark Word 裡記的不是自己,且發現原持有偏向鎖的執行緒已經執行完同步程式碼塊,會嘗試 CAS 把 Mark Word 中的改為自己的執行緒 ID。
輕量級鎖#
輕量級鎖就是通過 CAS 進行加鎖的。
JVM 會給執行緒的棧幀
自旋鎖#
輕量級鎖膨脹為重量級鎖前,執行緒在執行 monitorenter 指令進入等待佇列時,會通過自旋去嘗試獲得鎖。
如果自旋超過一定次數時還未拿到鎖,就會進入阻塞狀態,等待核心來排程。此時會發生核心態與使用者態之間的上下文切換,所以會影響效能(引入自旋鎖就是為了減少這個開銷)。
因為後面的執行緒也先進行自旋嘗試獲取鎖,所以這對於已被阻塞的那些執行緒來說,會不公平。
重量級鎖#
重量級鎖就是通過核心來操作執行緒。因為頻繁出現核心態與使用者態的切換,會嚴重影響效能。
升級為重量級鎖時會在堆中建立 monitor 物件,並將 Mark Word 指向該 monitor 物件。monitor 中有 cxq(ContentionList),EntryList ,WaitSet,owner:
鎖升級的流程圖#
圖片來自:Java Synchronised機制
鎖降級#
Hotspot 在 1.8 開始有了鎖降級。在 STW 期間 JVM 進入安全點時如果發現有閒置的 monitor(重量級鎖物件),會進行鎖降級。
為什麼鎖資訊存放在物件頭裡?#
因為在Java中任意物件都可以用作鎖,因此必定要有一個對映關係,儲存該物件以及其對應的鎖資訊(比如當前哪個執行緒持有鎖,哪些執行緒在等待)。一種很直觀的方法是,用一個全域性map,來儲存這個對映關係,但這樣會有一些問題:需要對map做執行緒安全保障,不同的
synchronized
之間會相互影響,效能差;另外當同步物件較多時,該map可能會佔用比較多的記憶體。所以最好的辦法是將這個對映關係儲存在物件頭中,因為物件頭本身也有一些hashcode、GC相關的資料,所以如果能將鎖資訊與這些資訊共存在物件頭中就好了。
也就是說,如果用一個全域性 map 來存物件的鎖資訊,還需要對該 map 做執行緒安全處理,不同的鎖之間會有影響。所以直接存到物件頭。
其他文章#
【死磕Java併發】—–深入分析synchronized的實現原理
【死磕 Java 併發】—– synchronized 的鎖膨脹過程
【轉載】Java中的鎖機制 synchronized & 偏向鎖 & 輕量級鎖 & 重量級鎖 & 各自優缺點及場景 & AtomicReference