鎖升級過程就是鎖優(yōu)化
在JDK最開始的時候synchronized
屬于重量級的鎖
佑刷,每次加鎖都是通過操作系統(tǒng)
來申請鎖
,所以會造成synchronized
的效率比較低,尤其是隨著時代的發(fā)展,多線程高并發(fā)越來越多杖剪,synchronized效率低的缺點就越來越明顯,所以jdk對它進行了優(yōu)化驰贷,不再是一開始就向操作系統(tǒng)申請鎖
盛嘿,分成偏向鎖 - 輕量級鎖 - 重量級鎖三個過程
。
要講鎖升級首先回顧一個知識點括袒,synchronized
實際上是對對象加鎖
的過程(鎖一段代碼則需指定對象次兆,如果鎖方法,普通方法鎖的是方法的對象锹锰,靜態(tài)方法則是這個方法所在類的calss對象)芥炭,在對象頭
的mark word
中最低的三位
代表鎖狀態(tài)
漓库,其中1位是偏向鎖位
,兩位是普通鎖位
园蝠,具體如下圖:
這次主要關注mark word的后三位的變化渺蒿,根據變化我們可以得出實際上對象的鎖狀態(tài)可以分成無鎖、偏向鎖砰琢、輕量級鎖蘸嘶、重量級鎖4個狀態(tài)
良瞧,GC過程中有對對象的鎖降級
陪汽。
那么接下來就看看鎖是如何升級的,首先最開始對象是無鎖狀態(tài)
褥蚯,當一個線程準備對這個對象加鎖前驗證這三個字節(jié)發(fā)現了無鎖狀態(tài)挚冤,把對象是否偏向設置為1,鎖標志位還是01赞庶,并把markword
的線程ID改為當前線程ID训挡,此時對象處于偏向鎖狀態(tài)
。
一個線程繼續(xù)
對該該對象加鎖歧强,發(fā)現是偏向鎖狀態(tài)澜薄,判斷偏向鎖線程是否是這個線程
,如果是則是直接進入摊册,如果偏向線程不是當前線程肤京,也就是存在鎖競爭
,那么就撤銷偏向鎖
茅特,升級為輕量級鎖
忘分。
輕量級鎖實現方式是各個線程在自己的線程棧生成LockRecord ,用CAS操作將markword設置為指向自己這個線程的LockRecord的指針白修,設置成功者得到鎖妒峦,沒有成功的將繼續(xù)使用CAS一直循環(huán)直到成功,所以輕量級鎖也叫自旋鎖兵睛。
JDK自旋有默認最大值是10次肯骇,JDK6對自旋鎖進行了優(yōu)化,自旋的時間不再是固定的祖很,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定的笛丙,比如當前線程在剛剛成功獲取過自旋鎖,那么虛擬機就會認為這次自旋也很有可能會成功突琳,那么循環(huán)次數就可以多進行幾次若债,這就叫自適應自旋。有了自適應自旋就不用我們設置最大循環(huán)次數拆融,由JVM監(jiān)控動態(tài)設置蠢琳。
自旋鎖有個缺點就是等待的線程仍然在自旋運行啊终,如果自旋的次數太多或者自旋等待的線程太多會造成CPU消耗過大,這種情況反而不如向操作系統(tǒng)來申請鎖傲须,阻塞其他線程蓝牲。所以在這種情況下鎖會升級成重量級鎖,沒有獲取到鎖的現在直接在系統(tǒng)級別被掛起泰讽,直到系統(tǒng)釋放鎖喚醒這些掛起的線程例衍,這些線程再次搶鎖。
鎖的升級過程畫了一個簡單的圖便于理解以上內容已卸,如下圖:
不重要的兩個鎖優(yōu)化
還有兩個不重要的鎖優(yōu)化還是要了解了解的佛玄。
第一個是鎖消除:當一段代碼中加了鎖,但是通過JVM分析他是線程安全的累澡,那么JVM會把鎖去掉梦抢。比如方法中一段代碼有加鎖,但是經過分析不會出現線程安全的問題愧哟,那么JVM就會把鎖給消除奥吩。
第二個是鎖粗化:當JVM檢測到一段連續(xù)的多次操作都在對同一個對象多次加鎖,那么JVM可能會優(yōu)化成對這整段加一個鎖蕊梧,沒有把加鎖的操作分的那么細霞赫,所以叫鎖粗化。
具體代碼案例如下圖:
總結
鎖升級主要分為偏向鎖 - 輕量級鎖 - 重量級鎖三層肥矢。
偏向鎖
端衰、輕量級鎖
是在Java內部的優(yōu)化,屬于所謂的用戶態(tài)
橄抹,而重量級鎖
則是向操作系統(tǒng)
申請靴迫,屬于內核態(tài)
。
在鎖競爭不激烈的時候由jvm自己解決肯定性能是最好的楼誓,但是jvm通過自旋方式
解決會消耗CPU性能玉锌,所以在鎖競爭激烈
的情況下重量級鎖性能更好
。
鎖升級是機制層面的優(yōu)化
疟羹,而鎖消除和鎖粗化則是jvm對代碼層面的優(yōu)化
主守。