溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Synchronized的輕量級(jí)鎖是否自旋

發(fā)布時(shí)間:2021-10-18 14:13:42 來(lái)源:億速云 閱讀:119 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容介紹了“Synchronized的輕量級(jí)鎖是否自旋”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

Synchronized的輕量級(jí)鎖是否自旋

鎖升級(jí)想必網(wǎng)上有太多文章說(shuō)過(guò)了,這里提到當(dāng)輕量級(jí)鎖 CAS 失敗,則當(dāng)前線程會(huì)嘗試使用自旋來(lái)獲取鎖。

其實(shí)起初我也是這樣認(rèn)為的,畢竟都是這樣說(shuō)的,而且也很有道理。

因?yàn)橹亓考?jí)鎖會(huì)阻塞線程,所以如果加鎖的代碼執(zhí)行的非???,那么稍微自旋一會(huì)兒其他線程就不需要鎖了,就可以直接 CAS 成功了,因此不用阻塞了線程然后再喚醒。

但是我看了源碼之后發(fā)現(xiàn)并不是這樣的,這段代碼在 synchronizer.cpp 中。

Synchronized的輕量級(jí)鎖是否自旋

所以 CAS 失敗了之后,并沒(méi)有什么自旋操作,如果 CAS 成功就直接 return 了,如果失敗會(huì)執(zhí)行下面的鎖膨脹方法。

我去鎖膨脹的代碼ObjectSynchronizer::inflate翻了翻,也沒(méi)看到自旋操作。

所以從源碼來(lái)看輕量級(jí)鎖 CAS 失敗并不會(huì)自旋而是直接膨脹成重量級(jí)鎖。

不過(guò)為了優(yōu)化性能,自旋操作在 Synchronized 中確實(shí)卻有。

那是在已經(jīng)升級(jí)成重量級(jí)鎖之后,線程如果沒(méi)有爭(zhēng)搶到鎖,會(huì)進(jìn)行一段自旋等待鎖的釋放。

咱們還是看源碼說(shuō)話,單單注釋其實(shí)就已經(jīng)說(shuō)得很清楚了:

Synchronized的輕量級(jí)鎖是否自旋

畢竟阻塞線程入隊(duì)再喚醒開(kāi)銷還是有點(diǎn)大的。

我們?cè)賮?lái)看看 TrySpin 的操作,這里面有自適應(yīng)自旋,其實(shí)從實(shí)際函數(shù)名就 TrySpin_VaryDuration 就可以反映出自旋是變化的。

Synchronized的輕量級(jí)鎖是否自旋

至此,有關(guān) Synchronized 自旋問(wèn)題就完結(jié)了,重量級(jí)鎖競(jìng)爭(zhēng)失敗會(huì)有自旋操作,輕量級(jí)鎖沒(méi)有這個(gè)動(dòng)作(至少 1.8 源碼是這樣的),如果有人反駁你,請(qǐng)把這篇文章甩給他哈哈。

不過(guò)都說(shuō)到這兒了,索性我就繼續(xù)講講 Synchronized 吧,畢竟這玩意出鏡率還是挺高的。

這篇文章關(guān)于 Synchronized 的深度到哪個(gè)程度呢?

之后如有面試官問(wèn)你看過(guò)啥源碼?

看完這篇文章,你可以回答:我看過(guò) JVM 的源碼。

當(dāng)然源碼有點(diǎn)多的,我把 Synchronized 相關(guān)的所有操作都過(guò)了一遍,還是有點(diǎn)難度的。

不過(guò)之前看過(guò)我的源碼分析的讀者就會(huì)知道,我都會(huì)畫(huà)個(gè)流程圖來(lái)整理的,所以即使代碼看不懂,流程還是可以搞清楚的!

好,發(fā)車!

從重量級(jí)鎖開(kāi)始說(shuō)起

Synchronized 在1.6 之前只是重量級(jí)鎖。

因?yàn)闀?huì)有線程的阻塞和喚醒,這個(gè)操作是借助操作系統(tǒng)的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)的,常見(jiàn)的 Linux 下就是利用 pthread 的 mutex 來(lái)實(shí)現(xiàn)的。

我截圖了調(diào)用線程阻塞的源碼,可以看到確實(shí)是利用了 mutex。

Synchronized的輕量級(jí)鎖是否自旋

而涉及到系統(tǒng)調(diào)用就會(huì)有上下文的切換,即用戶態(tài)和內(nèi)核態(tài)的切換,我們知道這種切換的開(kāi)銷還是挺大的。

所以稱為重量級(jí)鎖,也因?yàn)檫@樣才會(huì)有上面提到的自適應(yīng)自旋操作,因?yàn)椴幌M叩竭@一步呀!

我們來(lái)看看重量級(jí)鎖的實(shí)現(xiàn)原理

Synchronized 關(guān)鍵字可以修飾代碼塊,實(shí)例方法和靜態(tài)方法,本質(zhì)上都是作用于對(duì)象上。

代碼塊作用于括號(hào)里面的對(duì)象,實(shí)例方法是當(dāng)前的實(shí)例對(duì)象即 this ,而靜態(tài)方法就是當(dāng)前的類。

Synchronized的輕量級(jí)鎖是否自旋

這里有個(gè)概念叫臨界區(qū)。

我們知道,之所以會(huì)有競(jìng)爭(zhēng)是因?yàn)橛泄蚕碣Y源的存在,多個(gè)線程都想要得到那個(gè)共享資源,所以就劃分了一個(gè)區(qū)域,操作共享資源資源的代碼就在區(qū)域內(nèi)。

可以理解為想要進(jìn)入到這個(gè)區(qū)域就必須持有鎖,不然就無(wú)法進(jìn)入,這個(gè)區(qū)域叫臨界區(qū)。

當(dāng)用 Synchronized 修飾代碼塊時(shí)

此時(shí)編譯得到的字節(jié)碼會(huì)有 monitorenter 和 monitorexit 指令,我習(xí)慣按照臨界區(qū)來(lái)理解,enter 就是要進(jìn)入臨界區(qū)了,exit 就是要退出臨界區(qū)了,與之對(duì)應(yīng)的就是獲得鎖和解鎖。

實(shí)際上這兩個(gè)指令還是和修飾代碼塊的那個(gè)對(duì)象相關(guān)的,也就是上文代碼中的lockObject。

每個(gè)對(duì)象都有一個(gè) monitor 對(duì)象于之關(guān)聯(lián),執(zhí)行 monitorenter 指令的線程就是試圖去獲取 monitor 的所有權(quán),搶到了就是成功獲取鎖了。

這個(gè) monitor 下文會(huì)詳細(xì)分析,我們先看下生成的字節(jié)碼是怎樣的。

圖片上方是 lockObject 方法編譯得到的字節(jié)碼,下面就是 lockObject 方法,這樣對(duì)著看比較容易理解。

Synchronized的輕量級(jí)鎖是否自旋

從截圖來(lái)看,執(zhí)行 System.out 之前執(zhí)行了 monitorenter 執(zhí)行,這里執(zhí)行爭(zhēng)鎖動(dòng)作,拿到鎖即可進(jìn)入臨界區(qū)。

調(diào)用完之后有個(gè) monitorexit 指令,表示釋放鎖,要出臨界區(qū)了。

圖中我還標(biāo)了一個(gè) monitorexit 指令時(shí),因?yàn)橛挟惓5那闆r也需要解鎖,不然就死鎖了。

從生成的字節(jié)碼我們也可以得知,為什么 synchronized 不需要手動(dòng)解鎖?

是有人在替我們負(fù)重前行啊!編譯器生成的字節(jié)碼都幫咱們做好了,異常的情況也考慮到了。

當(dāng)用 synchronized 修飾方法時(shí)

修飾方法生成的字節(jié)碼和修飾代碼塊的不太一樣,但本質(zhì)上是一樣。

此時(shí)字節(jié)碼中沒(méi)有 monitorenter 和 monitorexit 指令,不過(guò)在當(dāng)前方法的訪問(wèn)標(biāo)記上做了手腳。

我這里用的是 idea 的插件來(lái)看字節(jié)碼,所以展示的字面結(jié)果不太一樣,不過(guò) flag 標(biāo)記是一樣的:0x0021 ,是 ACC_PUBLIC 和 ACC_SYNCHRONIZED 的結(jié)合。

Synchronized的輕量級(jí)鎖是否自旋

原理就是修飾方法的時(shí)候在 flag 上標(biāo)記 ACC_SYNCHRONIZED,在運(yùn)行時(shí)常量池中通過(guò) ACC_SYNCHRONIZED 標(biāo)志來(lái)區(qū)分,這樣 JVM 就知道這個(gè)方法是被 synchronized 標(biāo)記的,于是在進(jìn)入方法的時(shí)候就會(huì)進(jìn)行執(zhí)行爭(zhēng)鎖的操作,一樣只有拿到鎖才能繼續(xù)執(zhí)行。

然后不論是正常退出還是異常退出,都會(huì)進(jìn)行解鎖的操作,所以本質(zhì)還是一樣的。

這里還有個(gè)隱式的鎖對(duì)象就是我上面提到的,修飾實(shí)例方法就是 this,修飾類方法就是當(dāng)前類(關(guān)于這點(diǎn)是有坑的,我寫(xiě)的這篇文章分析過(guò))。

我還記得有個(gè)面試題,好像是面字節(jié)跳動(dòng)時(shí)候問(wèn)的,面試官問(wèn) synchronized  修飾方法和代碼塊的時(shí)候字節(jié)碼層面有什么區(qū)別?。

怎么說(shuō)?不知不覺(jué)距離字節(jié)跳動(dòng)又更近了呢。

Synchronized的輕量級(jí)鎖是否自旋

我們?cè)賮?lái)繼續(xù)深入 synchronized

從上文我們已經(jīng)知道 synchronized 是作用于對(duì)象身上的,但是沒(méi)細(xì)說(shuō),我們接下來(lái)剖析一波。

在 Java 中,對(duì)象結(jié)構(gòu)分為對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。

而對(duì)象頭又分為:MarkWord 、 klass pointer、數(shù)組長(zhǎng)度(只有數(shù)組才有),我們的重點(diǎn)是鎖,所以關(guān)注點(diǎn)只放在 MarkWord 上。

Synchronized的輕量級(jí)鎖是否自旋

我再畫(huà)一下 64 位時(shí) MarkWord 在不同狀態(tài)下的內(nèi)存布局(里面的 monitor 打錯(cuò)了,但是我不準(zhǔn)備改,留個(gè)印記哈哈)。

Synchronized的輕量級(jí)鎖是否自旋

MarkWord 結(jié)構(gòu)之所以搞得這么復(fù)雜,是因?yàn)樾枰?jié)省內(nèi)存,讓同一個(gè)內(nèi)存區(qū)域在不同階段有不同的用處。

記住這個(gè)圖啊,各種鎖操作都和這個(gè) MarkWord 有很強(qiáng)的聯(lián)系。

從圖中可以看到,在重量級(jí)鎖時(shí),對(duì)象頭的鎖標(biāo)記位為 10,并且會(huì)有一個(gè)指針指向這個(gè) monitor 對(duì)象,所以鎖對(duì)象和 monitor 兩者就是這樣關(guān)聯(lián)的。

Synchronized的輕量級(jí)鎖是否自旋

而這個(gè) monitor 在 HotSpot 中是 c++ 實(shí)現(xiàn)的,叫 ObjectMonitor,它是管程的實(shí)現(xiàn),也有叫監(jiān)視器的。

它長(zhǎng)這樣,重點(diǎn)字段我都注釋了含義,還專門(mén)截了個(gè)頭文件的注釋:

Synchronized的輕量級(jí)鎖是否自旋

暫時(shí)記憶一下,等下源碼和這幾個(gè)字段關(guān)聯(lián)很大。

synchronized 底層原理

先來(lái)一張圖,結(jié)合上面 monitor 的注釋,先看看,看不懂沒(méi)關(guān)系,有個(gè)大致流轉(zhuǎn)的印象即可:

Synchronized的輕量級(jí)鎖是否自旋

好,我們繼續(xù)。

前面我們提到了 monitorenter 這個(gè)指令,這個(gè)指令會(huì)執(zhí)行下面的代碼:

Synchronized的輕量級(jí)鎖是否自旋

我們現(xiàn)在分析的是重量級(jí)鎖,所以不關(guān)心偏向的代碼,而 slow_enter 方法文章一開(kāi)始的截圖就是了,最終會(huì)執(zhí)行到 ObjectMonitor::enter 這個(gè)方法中。

Synchronized的輕量級(jí)鎖是否自旋

可以看到重點(diǎn)就是通過(guò) CAS 把 ObjectMonitor 中的 _owner 設(shè)置為當(dāng)前線程,設(shè)置成功就表示獲取鎖成功。

然后通過(guò) recursions 的自增來(lái)表示重入。

如果 CAS 失敗的話,會(huì)執(zhí)行下面的一個(gè)循環(huán):

Synchronized的輕量級(jí)鎖是否自旋

EnterI 的代碼其實(shí)上面也已經(jīng)截圖了,這里再來(lái)一次,我把重要的入隊(duì)操作加上,并且刪除了一些不重要的代碼:

Synchronized的輕量級(jí)鎖是否自旋

先再嘗試一下獲取鎖,不行的話就自適應(yīng)自旋,還不行就包裝成 ObjectWaiter 對(duì)象加入到 _cxq 這個(gè)單向鏈表之中,掙扎一下還是沒(méi)搶到鎖的話,那么就要阻塞了,所以下面還有個(gè)阻塞的方法。

Synchronized的輕量級(jí)鎖是否自旋

可以看到不論哪個(gè)分支都會(huì)執(zhí)行 Self->_ParkEvent->park(),這個(gè)就是上文提到的調(diào)用 pthread_mutex_lock

至此爭(zhēng)搶鎖的流程已經(jīng)很清晰了,我再畫(huà)個(gè)圖來(lái)理一理。

Synchronized的輕量級(jí)鎖是否自旋

接下來(lái)再看看解鎖的方法

ObjectMonitor::exit 就是解鎖時(shí)會(huì)調(diào)用的方法。

Synchronized的輕量級(jí)鎖是否自旋

可重入鎖就是根據(jù) _recursions 來(lái)判斷的,重入一次 _recursions++,解鎖一次 _recursions--,如果減到 0 說(shuō)明需要釋放鎖了。

然后此時(shí)解鎖的線程還會(huì)喚醒之前等待的線程,這里有好幾種模式,我們來(lái)看看。

如果 QMode == 2 && _cxq != NULL的時(shí)候:

Synchronized的輕量級(jí)鎖是否自旋

如果QMode == 3 && _cxq != NULL的時(shí)候,我就截取了一部分代碼:

Synchronized的輕量級(jí)鎖是否自旋

如果 QMode == 4 && _cxq != NULL的時(shí)候:

Synchronized的輕量級(jí)鎖是否自旋

如果 QMode 不是 2 的話,最終會(huì)執(zhí)行:

Synchronized的輕量級(jí)鎖是否自旋

至此,解鎖的流程就完畢了!我再畫(huà)一波流程圖:

Synchronized的輕量級(jí)鎖是否自旋

接下來(lái)再看看調(diào)用 wait 的方法

沒(méi)啥花頭,就是將當(dāng)前線程加入到 _waitSet 這個(gè)雙向鏈表中,然后再執(zhí)行 ObjectMonitor::exit 方法來(lái)釋放鎖。

Synchronized的輕量級(jí)鎖是否自旋

接下來(lái)再看看調(diào)用 notify 的方法

也沒(méi)啥花頭,就是從 _waitSet 頭部拿節(jié)點(diǎn),然后根據(jù)策略選擇是放在 cxq 還是 EntryList 的頭部或者尾部,并且進(jìn)行喚醒。

Synchronized的輕量級(jí)鎖是否自旋

至于 notifyAll 我就不分析了,一樣的,無(wú)非就是做了個(gè)循環(huán),全部喚醒。

至此 synchronized 的幾個(gè)操作都齊活了,出去可以說(shuō)自己深入研究過(guò)  synchronized 了。

現(xiàn)在再來(lái)看下這個(gè)圖,應(yīng)該心里很有數(shù)了。

Synchronized的輕量級(jí)鎖是否自旋

為什么會(huì)有_cxq 和 _EntryList 兩個(gè)列表來(lái)放線程?

因?yàn)闀?huì)有多個(gè)線程會(huì)同時(shí)競(jìng)爭(zhēng)鎖,所以搞了個(gè) _cxq 這個(gè)單向鏈表基于 CAS 來(lái) hold 住這些并發(fā),然后另外搞一個(gè) _EntryList 這個(gè)雙向鏈表,來(lái)在每次喚醒的時(shí)候搬遷一些線程節(jié)點(diǎn),降低 _cxq 的尾部競(jìng)爭(zhēng)。

引入自旋

synchronized 的原理大致應(yīng)該都清晰了,我們也知道了底層會(huì)用到系統(tǒng)調(diào)用,會(huì)有較大的開(kāi)銷,那思考一下該如何優(yōu)化?

從小標(biāo)題就已經(jīng)知道了,方案就是自旋,文章開(kāi)頭就已經(jīng)說(shuō)了,這里再提一提。

自旋其實(shí)就是空轉(zhuǎn) CPU,執(zhí)行一些無(wú)意義的指令,目的就是不讓出 CPU 等待鎖的釋放。

正常情況下鎖獲取失敗就應(yīng)該阻塞入隊(duì),但是有時(shí)候可能剛一阻塞,別的線程就釋放鎖了,然后再喚醒剛剛阻塞的線程,這就沒(méi)必要了。

所以在線程競(jìng)爭(zhēng)不是很激烈的時(shí)候,稍微自旋一會(huì)兒,指不定不需要阻塞線程就能直接獲取鎖,這樣就避免了不必要的開(kāi)銷,提高了鎖的性能。

但是自旋的次數(shù)又是一個(gè)難點(diǎn),在競(jìng)爭(zhēng)很激烈的情況,自旋就是在浪費(fèi) CPU,因?yàn)榻Y(jié)果肯定是自旋一會(huì)讓之后阻塞。

所以 Java 引入的是自適應(yīng)自旋,根據(jù)上次自旋次數(shù),來(lái)動(dòng)態(tài)調(diào)整自旋的次數(shù),這就叫結(jié)合歷史經(jīng)驗(yàn)做事。

注意這是重量級(jí)鎖的步驟,別忘了文章開(kāi)頭說(shuō)的~。

至此,synchronized 重量級(jí)鎖的原理應(yīng)該就很清晰了吧? 小結(jié)一下

synchronized 底層是利用 monitor 對(duì)象,CAS 和 mutex 互斥鎖來(lái)實(shí)現(xiàn)的,內(nèi)部會(huì)有等待隊(duì)列(cxq 和 EntryList)和條件等待隊(duì)列(waitSet)來(lái)存放相應(yīng)阻塞的線程。

未競(jìng)爭(zhēng)到鎖的線程存儲(chǔ)到等待隊(duì)列中,獲得鎖的線程調(diào)用 wait 后便存放在條件等待隊(duì)列中,解鎖和 notify 都會(huì)喚醒相應(yīng)隊(duì)列中的等待線程來(lái)爭(zhēng)搶鎖。

然后由于阻塞和喚醒依賴于底層的操作系統(tǒng)實(shí)現(xiàn),系統(tǒng)調(diào)用存在用戶態(tài)與內(nèi)核態(tài)之間的切換,所以有較高的開(kāi)銷,因此稱之為重量級(jí)鎖。

所以又引入了自適應(yīng)自旋機(jī)制,來(lái)提高鎖的性能。

現(xiàn)在要引入輕量級(jí)鎖了

我們?cè)偎伎家幌拢欠裼羞@樣的場(chǎng)景:多個(gè)線程都是在不同的時(shí)間段來(lái)請(qǐng)求同一把鎖,此時(shí)根本就用不需要阻塞線程,連 monitor 對(duì)象都不需要,所以就引入了輕量級(jí)鎖這個(gè)概念,避免了系統(tǒng)調(diào)用,減少了開(kāi)銷。

在鎖競(jìng)爭(zhēng)不激烈的情況下,這種場(chǎng)景還是很常見(jiàn)的,可能是常態(tài),所以輕量級(jí)鎖的引入很有必要。

在介紹輕量級(jí)鎖的原理之前,再看看之前 MarkWord 圖。

Synchronized的輕量級(jí)鎖是否自旋

輕量級(jí)鎖操作的就是對(duì)象頭的 MarkWord 。

如果判斷當(dāng)前處于無(wú)鎖狀態(tài),會(huì)在當(dāng)前線程棧的當(dāng)前棧幀中劃出一塊叫 LockRecord 的區(qū)域,然后把鎖對(duì)象的 MarkWord 拷貝一份到 LockRecord 中稱之為 dhw(就是那個(gè)set_displaced_header 方法執(zhí)行的)里。

然后通過(guò) CAS 把鎖對(duì)象頭指向這個(gè) LockRecord 。

輕量級(jí)鎖的加鎖過(guò)程:

Synchronized的輕量級(jí)鎖是否自旋

如果當(dāng)前是有鎖狀態(tài),并且是當(dāng)前線程持有的,則將 null 放到 dhw 中,這是重入鎖的邏輯。

Synchronized的輕量級(jí)鎖是否自旋

我們?cè)倏聪螺p量級(jí)鎖解鎖的邏輯:

Synchronized的輕量級(jí)鎖是否自旋

邏輯還是很簡(jiǎn)單的,就是要把當(dāng)前棧幀中 LockRecord 存儲(chǔ)的 markword (dhw)通過(guò) CAS 換回到對(duì)象頭中。

如果獲取到的 dhw 是 null 說(shuō)明此時(shí)是重入的,所以直接返回即可,否則就是利用 CAS 換,如果 CAS 失敗說(shuō)明此時(shí)有競(jìng)爭(zhēng),那么就膨脹!

Synchronized的輕量級(jí)鎖是否自旋

關(guān)于這個(gè)輕量級(jí)加鎖我再多說(shuō)幾句。

每次加鎖肯定是在一個(gè)方法調(diào)用中,而方法調(diào)用就是有棧幀入棧,如果是輕量級(jí)鎖重入的話那么此時(shí)入棧的棧幀里面的 dhw 就是 null,否則就是鎖對(duì)象的 markword。

這樣在解鎖的時(shí)候就能通過(guò) dhw 的值來(lái)判斷此時(shí)是否是重入的。

現(xiàn)在要引入偏向鎖

我們?cè)偎伎家幌拢欠裼羞@樣的場(chǎng)景:一開(kāi)始一直只有一個(gè)線程持有這個(gè)鎖,也不會(huì)有其他線程來(lái)競(jìng)爭(zhēng),此時(shí)頻繁的 CAS 是沒(méi)有必要的,CAS 也是有開(kāi)銷的。

所以 JVM 研究者們就搞了個(gè)偏向鎖,就是偏向一個(gè)線程,那么這個(gè)線程就可以直接獲得鎖。

我們?cè)倏纯催@個(gè)圖,偏向鎖在第二行。

Synchronized的輕量級(jí)鎖是否自旋

原理也不難,如果當(dāng)前鎖對(duì)象支持偏向鎖,那么就會(huì)通過(guò) CAS 操作:將當(dāng)前線程的地址(也當(dāng)做唯一ID)記錄到 markword 中,并且將標(biāo)記字段的最后三位設(shè)置為 101。

之后有線程請(qǐng)求這把鎖,只需要判斷 markword 最后三位是否為 101,是否指向的是當(dāng)前線程的地址。

還有一個(gè)可能很多文章會(huì)漏的點(diǎn),就是還需要判斷 epoch 值是否和鎖對(duì)象的類中的 epoch 值相同。

如果都滿足,那么說(shuō)明當(dāng)前線程持有該偏向鎖,就可以直接返回。

這 epoch 干啥用的?

Synchronized的輕量級(jí)鎖是否自旋

可以理解為是第幾代偏向鎖。

偏向鎖在有競(jìng)爭(zhēng)的時(shí)候是要執(zhí)行撤銷操作的,其實(shí)就是要升級(jí)成輕量級(jí)鎖。

而當(dāng)一類對(duì)象撤銷的次數(shù)過(guò)多,比如有個(gè) Yes 類的對(duì)象作為偏向鎖,經(jīng)常被撤銷,次數(shù)到了一定閾值(XX:BiasedLockingBulkRebiasThreshold,默認(rèn)為 20 )就會(huì)把當(dāng)代的偏向鎖廢棄,把類的 epoch 加一。

所以當(dāng)類對(duì)象和鎖對(duì)象的 epoch 值不等的時(shí)候,當(dāng)前線程可以將該鎖重偏向至自己,因?yàn)榍耙淮蜴i已經(jīng)廢棄了。

不過(guò)為保證正在執(zhí)行的持有鎖的線程不能因?yàn)檫@個(gè)而丟失了鎖,偏向鎖撤銷需要所有線程處于安全點(diǎn),然后遍歷所有線程的 Java 棧,找出該類已加鎖的實(shí)例,并且將它們標(biāo)記字段中的 epoch 值加 1。

當(dāng)撤銷次數(shù)超過(guò)另一個(gè)閾值(XX:BiasedLockingBulkRevokeThreshold,默認(rèn)值為 40),則廢棄此類的偏向功能,也就是說(shuō)這個(gè)類都無(wú)法偏向了。

至此整個(gè) Synchronized 的流程應(yīng)該都比較清楚了。

我是反著來(lái)講鎖升級(jí)的過(guò)程的,因?yàn)槭聦?shí)上是先有的重量級(jí)鎖,然后根據(jù)實(shí)際分析優(yōu)化得到的偏向鎖和輕量級(jí)鎖。

Synchronized的輕量級(jí)鎖是否自旋

包括期間的一些細(xì)節(jié)應(yīng)該也較為清楚了,我覺(jué)得對(duì)于 Synchronized 了解到這份上差不多了。

我再搞了張 openjdk wiki 上的圖,看看是不是很清晰了:

Synchronized的輕量級(jí)鎖是否自旋

“Synchronized的輕量級(jí)鎖是否自旋”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI