偏向鎖到底是怎麼個回事啊啊啊啊

(給IT一刻鐘加星標,可以迎娶白富美)

微信公衆號:IT一刻鐘
大型現實非嚴肅主義現場
一刻鐘與你分享優質技術架構與見聞,做一個有劇情的程序員
關注可第一時間瞭解更多精彩內容,定期有福利相送喲。

話說有這麼一件事。

於是當天夜裏,小哥哥便哼哧哼哧的畫出了偏向鎖的邏輯圖。
其邏輯呢,各位看官待我慢慢道來。

看一張大圖

點擊看大圖

偏向鎖邏輯圖

流程講解

友情提示:在閱讀下方內容之前,建議先看完上一篇《且聽我一個故事講透一個鎖原理之synchronized》,這樣可以更容易理解下方內容。

當JVM啓用了偏向鎖模式(JDK6以上默認開啓),新創建對象的Mark Word中的Thread Id爲0,說明此時處於可偏向但未偏向任何線程,也叫做匿名偏向狀態(anonymously biased)。

偏向鎖邏輯
1.線程A第一次訪問同步塊時,先檢測對象頭Mark Word中的標誌位是否爲01,依此判斷此時對象鎖是否處於無所狀態或者偏向鎖狀態(匿名偏向鎖);

2.然後判斷偏向鎖標誌位是否爲1,如果不是,則進入輕量級鎖邏輯(使用CAS競爭鎖),如果是,則進入下一步流程;

3.判斷是偏向鎖時,檢查對象頭Mark Word中記錄的Thread Id是否是當前線程ID,如果是,則表明當前線程已經獲得對象鎖,以後該線程進入同步塊時,不需要CAS進行加鎖,只會往當前線程的棧中添加一條Displaced Mark Word爲空的Lock Record中,用來統計重入的次數(如圖爲當對象所處於偏向鎖時,當前線程重入3次,線程棧幀中Lock Record記錄)。

偏向鎖重入

退出同步塊釋放偏向鎖時,則依次刪除對應Lock Record,但是不會修改對象頭中的Thread Id;

注:偏向鎖撤銷是指在獲取偏向鎖的過程中因不滿足條件導致要將鎖對象改爲非偏向鎖狀態,而偏向鎖釋放是指退出同步塊時的過程。

4.如果對象頭Mark Word中Thread Id不是當前線程ID,則進行CAS操作,企圖將當前線程ID替換進Mark Word。如果當前對象鎖狀態處於匿名偏向鎖狀態(可偏向未鎖定),則會替換成功(將Mark Word中的Thread id由匿名0改成當前線程ID,在當前線程棧中找到內存地址最高的可用Lock Record,將線程ID存入),獲取到鎖,執行同步代碼塊;

5.如果對象鎖已經被其他線程佔用,則會替換失敗,開始進行偏向鎖撤銷,這也是偏向鎖的特點,一旦出現線程競爭,就會撤銷偏向鎖;

6.偏向鎖的撤銷需要等待全局安全點(safe point,代表了一個狀態,在該狀態下所有線程都是暫停的),暫停持有偏向鎖的線程,檢查持有偏向鎖的線程狀態(遍歷當前JVM的所有線程,如果能找到,則說明偏向的線程還存活),如果線程還存活,則檢查線程是否在執行同步代碼塊中的代碼,如果是,則升級爲輕量級鎖,進行CAS競爭鎖;

注:每次進入同步塊(即執行monitorenter)的時候都會以從高往低的順序在棧中找到第一個可用的Lock Record,並設置偏向線程ID;每次解鎖(即執行monitorexit)的時候都會從最低的一個Lock Record移除。所以如果能找到對應的Lock Record說明偏向的線程還在執行同步代碼塊中的代碼。

7.如果持有偏向鎖的線程未存活,或者持有偏向鎖的線程未在執行同步代碼塊中的代碼,則進行校驗是否允許重偏向,如果不允許重偏向,則撤銷偏向鎖,將Mark Word設置爲無鎖狀態(未鎖定不可偏向狀態),然後升級爲輕量級鎖,進行CAS競爭鎖;

8.如果允許重偏向,設置爲匿名偏向鎖狀態,CAS將偏向鎖重新指向線程A(在對象頭和線程棧幀的鎖記錄中存儲當前線程ID);

9.喚醒暫停的線程,從安全點繼續執行代碼。

以上便是偏向鎖的整個邏輯了。

延申知識

批量重偏向與批量撤銷
淵源:從偏向鎖的加鎖解鎖過程中可看出,當只有一個線程反覆進入同步塊時,偏向鎖帶來的性能開銷基本可以忽略,但是當有其他線程嘗試獲得鎖時,就需要等到safe point時,再將偏向鎖撤銷爲無鎖狀態或升級爲輕量級,會消耗一定的性能,所以在多線程競爭頻繁的情況下,偏向鎖不僅不能提高性能,還會導致性能下降。
於是,就有了批量重偏向與批量撤銷的機制。

解決場景
批量重偏向(bulk rebias)機制是爲了解決:一個線程創建了大量對象並執行了初始的同步操作,後來另一個線程也來將這些對象作爲鎖對象進行操作,這樣會導致大量的偏向鎖撤銷操作
批量撤銷(bulk revoke)機制是爲了解決:在明顯多線程競爭劇烈的場景下使用偏向鎖是不合適的

原理
以class爲單位,爲每個class維護一個偏向鎖撤銷計數器,每一次該class的對象發生偏向撤銷操作時,該計數器+1,當這個值達到重偏向閾值(默認20)時,JVM就認爲該class的偏向鎖有問題,因此會進行批量重偏向。
每個class對象會有一個對應的epoch字段,每個處於偏向鎖狀態對象的Mark Word中也有該字段,其初始值爲創建該對象時class中的epoch的值。
每次發生批量重偏向時,就將該值+1,同時遍歷JVM中所有線程的棧,找到該class所有正處於加鎖狀態的偏向鎖,將其epoch字段改爲新值。下次獲得鎖時,發現當前對象的epoch值和class的epoch不相等,那就算當前已經偏向了其他線程,也不會執行撤銷操作,而是直接通過CAS操作將其Mark Word的Thread Id 改成當前線程Id。
當達到重偏向閾值後,假設該class計數器繼續增長,當其達到批量撤銷的閾值後(默認40),JVM就認爲該class的使用場景存在多線程競爭,會標記該class爲不可偏向,之後,對於該class的鎖,直接走輕量級鎖的邏輯。


ru guo jue de bu cuo, dian ji yi xia guang gao zhi chi wo yi xia ~


推薦閱讀

從RocketMQ我們學到了什麼?(NameServer篇)

一篇讀懂分佈式架構下的負載均衡

你與解決“緩存污染”只差這篇文章的距離


本文分享自微信公衆號 - IT一刻鐘(it_info)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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