JAVA鎖系列(一)----syschronized鎖的優化---看完再也不怕面試問到鎖升級了!

Monitor對象:

鎖機制的底層同步工具-----monitor

在每個java對象裏,都存在一個內部鎖----monitor鎖
作爲線程的私有數據結構:每個線程都有一個可用的monitor record列表
monitor中會有一個owner字段存放有一個唯一標識,表示該鎖被我這個線程所佔有。
對象裏有對象頭----對象頭的底層結構由**markword(標記字段)和Klass pointer(類型指針)**構成,類型指針指向它的類元數據,來確定是哪個類的實例。
對象頭的markword裏有lockword指針----指向monitor的起始地址,monitor有個字段own字段再存放唯一標識。
monitor的本質是依賴於底層操作系統的Mutex Lock實現,線程之間切換需要將用戶態切換到內核態,成本很高,jdk1.6已將syschonized優化爲鎖升級模式,不會直接變爲重量級鎖,會進行鎖升級。

syschronized鎖升級得完整過程:

無鎖——>偏向鎖——>輕量級鎖——>重量級鎖

一:無鎖狀態:

對象頭前8字節markword得內存佈局:

鎖狀態 26位 31位 3位 4位 1bit 2bit
無鎖態 unused hashcode unamed 分代年齡 0 0 1

當資源數上升後,鎖升級

二:偏向鎖狀態:

偏向鎖: 對第一個線程進行偏向,它不需要申請,可以直接使用資源,jvm加鎖的方法,stringbuffer等等在被調用時,實際上只有一個線程在運行,所以纔會產生了偏向鎖這種東西。

對象頭前8字節markword得內存佈局:

鎖狀態 54位 2位 1位 4位 1bit 2bit
偏向鎖 當前線程指針 Epoch unused 分代年齡 1 0 1

一般再4s鍾之後纔會啓動偏向鎖。因爲,當jvm剛運行的時候,裏面有很多syn的方法,如果還是由偏向鎖再到自旋鎖,這樣會消耗大量資源。所以在4s內jvm知道是多線程,所以直接跳過了偏向鎖,轉變爲自旋鎖

當資源繼續變多後。鎖繼續升級。

三:輕量級鎖—自旋鎖狀態:

對象頭前8字節markword得內存佈局:

鎖狀態 62位 2bit
自旋鎖 指向線程棧中lockRecord的指針 0 0

當資源競爭變多時,我們將其升級爲輕量級鎖----CAS鎖,多個資源進行爭搶,都把值讀取然後去修改並加上自己的id號,誰先改完往回放貼上id號,這個鎖就給誰,剩下來只能在外面自旋,不斷改值,再去比較,看看id號有沒有被撤銷,沒又得話繼續自旋。

PS:CAS簡介:
CAS: compare and swap -----自旋鎖

第一步:

讀當前值E(從內存拿到線程cache裏)——>計算結果V——>準備把值寫回內存時——比較E值有沒有發生改變,改變了說明其他線程修改過,CAS會繼續讀這個改過的值再次嘗試計算結果,再寫回主存,再判斷有沒有改過,。。。。產生自旋,也就是有可能死循環。

相同的話也有可能被其他線程改過,不過改的值剛好沒變,這就是造成ABA問題。解決辦法就是加一個版本號就行了,就專門的類

再自己的線程棧裏生成lockrecord,當誰能將lockrecord得指針值寫到這對象得對象頭裏,就算搶到鎖了。lockrecord作用:記錄上一個狀態偏向鎖得值。

但是自旋會消耗cpu資源,非常多得資源進行爭搶時,只有一個資源能拿到,其他都在空自旋浪費資源,判斷方式(1.6之前自旋得次數或者等待得線程超過cpu核數得二分之一,1.6之後就變成自適應,jvm自己優化)此時升級爲重量級鎖

四:重量級鎖狀態:

在這裏插入圖片描述
對象頭前8字節markword得內存佈局:

鎖狀態 62位 2bit
重量級鎖 指向互斥量(重量級)的指針 1 0

重量級鎖向內核態申請資源,當一個拿到鎖後,剩下來得會進入一個等待隊列,只有到你的時候,你才能運行,這樣不會消耗cpu資源,但是會等的很慢。

引申概念:

鎖粗化:

我們在使用同步鎖時,需要將同步塊作用範圍儘可能縮小,使同步的操作數量縮小,讓競爭等待鎖的線程儘快拿到鎖
但是會有一個情況例外,如果我們一直是對同一個對象進行不斷的加鎖解鎖操作,本身這些操作就會造成不必要的性能損耗,jvm檢測到後會將枷鎖解鎖操作移到for循環外,形成一個更大範圍的加解鎖操作,完成這個的過程叫鎖粗化

鎖消除:

如果一段代碼的對象的數據,或者說共享的數據,其他線程壓根不會去訪問它,那就把它們當作棧上的數據對待,認爲他們是線程私有得,可以將加鎖去除,這其中的判斷依據需要通過逃逸分析來分析完成。

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