偏向鎖
偏向第一個拿到鎖的線程。
即第一個拿到鎖的線程,鎖會在對象頭 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:
鎖升級的流程圖
圖片來自:Java Synchronised機制
鎖降級
Hotspot 在 1.8 開始有了鎖降級。在 STW 期間 JVM 進入安全點時如果發現有閑置的 monitor(重量級鎖對象),會進行鎖降級。
為什么鎖信息存放在對象頭里?
因為在Java中任意對象都可以用作鎖,因此必定要有一個映射關系,存儲該對象以及其對應的鎖信息(比如當前哪個線程持有鎖,哪些線程在等待)。一種很直觀的方法是,用一個全局map,來存儲這個映射關系,但這樣會有一些問題:需要對map做線程安全保障,不同的
synchronized
之間會相互影響,性能差;另外當同步對象較多時,該map可能會占用比較多的內存。所以最好的辦法是將這個映射關系存儲在對象頭中,因為對象頭本身也有一些hashcode、GC相關的數據,所以如果能將鎖信息與這些信息共存在對象頭中就好了。
也就是說,如果用一個全局 map 來存對象的鎖信息,還需要對該 map 做線程安全處理,不同的鎖之間會有影響。所以直接存到對象頭。
其他文章
【死磕Java並發】—–深入分析synchronized的實現原理
【死磕 Java 並發】—– synchronized 的鎖膨脹過程
【轉載】Java中的鎖機制 synchronized & 偏向鎖 & 輕量級鎖 & 重量級鎖 & 各自優缺點及場景 & AtomicReference