微信公衆號:IT一刻鐘 。大型現實非嚴肅主義現場 一刻鐘與你分享優質技術架構與見聞,作一個有劇情的程序員。 關注可第一時間瞭解更多精彩內容,按期有福利相送喲。程序員
話說有這麼一件事。安全
因而當天夜裏,小哥哥便哼哧哼哧的畫出了偏向鎖的邏輯圖。 其邏輯呢,各位看官待我慢慢道來。微信
(點擊看大圖) 多線程
當JVM啓用了偏向鎖模式(JDK6以上默認開啓),新建立對象的Mark Word中的Thread Id爲0,說明此時處於可偏向但未偏向任何線程,也叫作匿名偏向狀態(anonymously biased)。架構
偏向鎖邏輯性能
1.線程A第一次訪問同步塊時,先檢測對象頭Mark Word中的標誌位是否爲01,依此判斷此時對象鎖是否處於無所狀態或者偏向鎖狀態(匿名偏向鎖);spa
2.而後判斷偏向鎖標誌位是否爲1,若是不是,則進入輕量級鎖邏輯(使用CAS競爭鎖),若是是,則進入下一步流程;線程
3.判斷是偏向鎖時,檢查對象頭Mark Word中記錄的Thread Id是不是當前線程ID,若是是,則代表當前線程已經得到對象鎖,之後該線程進入同步塊時,不須要CAS進行加鎖,只會往當前線程的棧中添加一條Displaced Mark Word爲空的Lock Record中,用來統計重入的次數(如圖爲當對象所處於偏向鎖時,當前線程重入3次,線程棧幀中Lock Record記錄)。 3d
退出同步塊釋放偏向鎖時,則依次刪除對應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的鎖,直接走輕量級鎖的邏輯。