synchronized的鎖升級、鎖膨脹

閱讀文本大概需要3分鐘。

0x01:偏向鎖

偏向第一個拿到鎖的線程。

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

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

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


0x02:輕量級鎖

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

JVM 會給線程的棧幀中創建一個叫鎖記錄 Lock Record 的空間,把對象頭 Mark Word 複製到該空間裏(Displaced Mark Word),並通過CAS 嘗試把原對象頭 Mark Word 中鎖記錄指針指向該鎖記錄。如果成功,表示線程拿到了鎖。如果失敗,則進行自旋(自旋鎖),自旋超過一定次數時升級爲重量級鎖,這時該線程會被內核掛起。


0x03:自旋鎖

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

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

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


0x04: 重量級鎖

重量級鎖就是通過內核來操作線程。因爲頻繁出現內核態與用戶態的切換,會嚴重影響性能。

升級爲重量級鎖時會在堆中創建 monitor 對象,並將 Mark Word 指向該 monitor 對象。monitor 中有 cxq(ContentionList),EntryList ,WaitSet,owner:

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

鎖升級的流程圖

圖片來自:Java Synchronised機制

0x05:鎖降級

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

Java鎖優化--JVM鎖降級

0x06:爲什麼鎖信息存放在對象頭裏?

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

因爲在Java中任意對象都可以用作鎖,因此必定要有一個映射關係,存儲該對象以及其對應的鎖信息(比如當前哪個線程持有鎖,哪些線程在等待)。一種很直觀的方法是,用一個全局map,來存儲這個映射關係,但這樣會有一些問題:需要對map做線程安全保障,不同的synchronized之間會相互影響,性能差;另外當同步對象較多時,該map可能會佔用比較多的內存。

所以最好的辦法是將這個映射關係存儲在對象頭中,因爲對象頭本身也有一些hashcode、GC相關的數據,所以如果能將鎖信息與這些信息共存在對象頭中就好了。

也就是說,如果用一個全局 map 來存對象的鎖信息,還需要對該 map 做線程安全處理,不同的鎖之間會有影響。所以直接存到對象頭。

推薦閱讀

Spring Boot 最流行的 16 條實踐

SSM框架的面試常見問題

【分佈式】緩存穿透、緩存雪崩,緩存擊穿解決方案

阿里P7給出的一份超詳細 Spring Boot 知識清單

關注我每天進步一點點

你點的每個在看,我都認真當成了喜歡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章