鎖升級(jí)過(guò)程就是鎖優(yōu)化
在JDK最開(kāi)始的時(shí)候synchronized屬于重量級(jí)的鎖,每次加鎖都是通過(guò)操作系統(tǒng)來(lái)申請(qǐng)鎖,所以會(huì)造成synchronized的效率比較低,尤其是隨著時(shí)代的發(fā)展,多線(xiàn)程高并發(fā)越來(lái)越多,synchronized效率低的缺點(diǎn)就越來(lái)越明顯,所以jdk對(duì)它進(jìn)行了優(yōu)化,不再是一開(kāi)始就向操作系統(tǒng)申請(qǐng)鎖,分成偏向鎖 - 輕量級(jí)鎖 - 重量級(jí)鎖三個(gè)過(guò)程。
要講鎖升級(jí)首先回顧一個(gè)知識(shí)點(diǎn),synchronized實(shí)際上是對(duì)對(duì)象加鎖的過(guò)程(鎖一段代碼則需指定對(duì)象,如果鎖方法,普通方法鎖的是方法的對(duì)象,靜態(tài)方法則是這個(gè)方法所在類(lèi)的calss對(duì)象),在對(duì)象頭的mark word中最低的三位代表鎖狀態(tài),其中1位是偏向鎖位,兩位是普通鎖位,具體如下圖:

這次主要關(guān)注mark word的后三位的變化,根據(jù)變化我們可以得出實(shí)際上對(duì)象的鎖狀態(tài)可以分成無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖4個(gè)狀態(tài),GC過(guò)程中有對(duì)對(duì)象的鎖降級(jí)。
那么接下來(lái)就看看鎖是如何升級(jí)的,首先最開(kāi)始對(duì)象是無(wú)鎖狀態(tài),當(dāng)一個(gè)線(xiàn)程準(zhǔn)備對(duì)這個(gè)對(duì)象加鎖前驗(yàn)證這三個(gè)字節(jié)發(fā)現(xiàn)了無(wú)鎖狀態(tài),把對(duì)象是否偏向設(shè)置為1,鎖標(biāo)志位還是01,并把markword的線(xiàn)程ID改為當(dāng)前線(xiàn)程ID,此時(shí)對(duì)象處于偏向鎖狀態(tài)。
一個(gè)線(xiàn)程繼續(xù)對(duì)該該對(duì)象加鎖,發(fā)現(xiàn)是偏向鎖狀態(tài),判斷偏向鎖線(xiàn)程是否是這個(gè)線(xiàn)程,如果是則是直接進(jìn)入,如果偏向線(xiàn)程不是當(dāng)前線(xiàn)程,也就是存在鎖競(jìng)爭(zhēng),那么就撤銷(xiāo)偏向鎖,升級(jí)為輕量級(jí)鎖。
輕量級(jí)鎖實(shí)現(xiàn)方式是各個(gè)線(xiàn)程在自己的線(xiàn)程棧生成LockRecord ,用CAS操作將markword設(shè)置為指向自己這個(gè)線(xiàn)程的LockRecord的指針,設(shè)置成功者得到鎖,沒(méi)有成功的將繼續(xù)使用CAS一直循環(huán)直到成功,所以輕量級(jí)鎖也叫自旋鎖。
JDK自旋有默認(rèn)最大值是10次,JDK6對(duì)自旋鎖進(jìn)行了優(yōu)化,自旋的時(shí)間不再是固定的,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定的,比如當(dāng)前線(xiàn)程在剛剛成功獲取過(guò)自旋鎖,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能會(huì)成功,那么循環(huán)次數(shù)就可以多進(jìn)行幾次,這就叫自適應(yīng)自旋。有了自適應(yīng)自旋就不用我們?cè)O(shè)置最大循環(huán)次數(shù),由JVM監(jiān)控動(dòng)態(tài)設(shè)置。
自旋鎖有個(gè)缺點(diǎn)就是等待的線(xiàn)程仍然在自旋運(yùn)行,如果自旋的次數(shù)太多或者自旋等待的線(xiàn)程太多會(huì)造成CPU消耗過(guò)大,這種情況反而不如向操作系統(tǒng)來(lái)申請(qǐng)鎖,阻塞其他線(xiàn)程。所以在這種情況下鎖會(huì)升級(jí)成重量級(jí)鎖,沒(méi)有獲取到鎖的現(xiàn)在直接在系統(tǒng)級(jí)別被掛起,直到系統(tǒng)釋放鎖喚醒這些掛起的線(xiàn)程,這些線(xiàn)程再次搶鎖。
鎖的升級(jí)過(guò)程畫(huà)了一個(gè)簡(jiǎn)單的圖便于理解以上內(nèi)容,如下圖:

不重要的兩個(gè)鎖優(yōu)化
還有兩個(gè)不重要的鎖優(yōu)化還是要了解了解的。
第一個(gè)是鎖消除:當(dāng)一段代碼中加了鎖,但是通過(guò)JVM分析他是線(xiàn)程安全的,那么JVM會(huì)把鎖去掉。比如方法中一段代碼有加鎖,但是經(jīng)過(guò)分析不會(huì)出現(xiàn)線(xiàn)程安全的問(wèn)題,那么JVM就會(huì)把鎖給消除。
第二個(gè)是鎖粗化:當(dāng)JVM檢測(cè)到一段連續(xù)的多次操作都在對(duì)同一個(gè)對(duì)象多次加鎖,那么JVM可能會(huì)優(yōu)化成對(duì)這整段加一個(gè)鎖,沒(méi)有把加鎖的操作分的那么細(xì),所以叫鎖粗化。
具體代碼案例如下圖:

總結(jié)
鎖升級(jí)主要分為偏向鎖 - 輕量級(jí)鎖 - 重量級(jí)鎖三層。
偏向鎖、輕量級(jí)鎖是在Java內(nèi)部的優(yōu)化,屬于所謂的用戶(hù)態(tài),而重量級(jí)鎖則是向操作系統(tǒng)申請(qǐng),屬于內(nèi)核態(tài)。
在鎖競(jìng)爭(zhēng)不激烈的時(shí)候由jvm自己解決肯定性能是最好的,但是jvm通過(guò)自旋方式解決會(huì)消耗CPU性能,所以在鎖競(jìng)爭(zhēng)激烈的情況下重量級(jí)鎖性能更好。
鎖升級(jí)是機(jī)制層面的優(yōu)化,而鎖消除和鎖粗化則是jvm對(duì)代碼層面的優(yōu)化。