本篇內(nèi)容介紹了“Synchronized的輕量級(jí)鎖是否自旋”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
成都創(chuàng)新互聯(lián)公司專(zhuān)注為客戶(hù)提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、楊浦網(wǎng)絡(luò)推廣、微信平臺(tái)小程序開(kāi)發(fā)、楊浦網(wǎng)絡(luò)營(yíng)銷(xiāo)、楊浦企業(yè)策劃、楊浦品牌公關(guān)、搜索引擎seo、人物專(zhuān)訪(fǎng)、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供楊浦建站搭建服務(wù),24小時(shí)服務(wù)熱線(xiàn):18980820575,官方網(wǎng)址:www.cdcxhl.com
鎖升級(jí)想必網(wǎng)上有太多文章說(shuō)過(guò)了,這里提到當(dāng)輕量級(jí)鎖 CAS 失敗,則當(dāng)前線(xiàn)程會(huì)嘗試使用自旋來(lái)獲取鎖。
其實(shí)起初我也是這樣認(rèn)為的,畢竟都是這樣說(shuō)的,而且也很有道理。
因?yàn)橹亓考?jí)鎖會(huì)阻塞線(xiàn)程,所以如果加鎖的代碼執(zhí)行的非常快,那么稍微自旋一會(huì)兒其他線(xiàn)程就不需要鎖了,就可以直接 CAS 成功了,因此不用阻塞了線(xiàn)程然后再喚醒。
但是我看了源碼之后發(fā)現(xiàn)并不是這樣的,這段代碼在 synchronizer.cpp 中。
所以 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í)鎖之后,線(xiàn)程如果沒(méi)有爭(zhēng)搶到鎖,會(huì)進(jìn)行一段自旋等待鎖的釋放。
咱們還是看源碼說(shuō)話(huà),單單注釋其實(shí)就已經(jīng)說(shuō)得很清楚了:
畢竟阻塞線(xiàn)程入隊(duì)再喚醒開(kāi)銷(xiāo)還是有點(diǎn)大的。
我們?cè)賮?lái)看看 TrySpin
的操作,這里面有自適應(yīng)自旋,其實(shí)從實(shí)際函數(shù)名就 TrySpin_VaryDuration
就可以反映出自旋是變化的。
至此,有關(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ā)車(chē)!
Synchronized 在1.6 之前只是重量級(jí)鎖。
因?yàn)闀?huì)有線(xiàn)程的阻塞和喚醒,這個(gè)操作是借助操作系統(tǒng)的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)的,常見(jiàn)的 Linux 下就是利用 pthread 的 mutex 來(lái)實(shí)現(xiàn)的。
我截圖了調(diào)用線(xiàn)程阻塞的源碼,可以看到確實(shí)是利用了 mutex。
而涉及到系統(tǒng)調(diào)用就會(huì)有上下文的切換,即用戶(hù)態(tài)和內(nèi)核態(tài)的切換,我們知道這種切換的開(kāi)銷(xiāo)還是挺大的。
所以稱(chēng)為重量級(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)前的類(lèi)。
這里有個(gè)概念叫臨界區(qū)。
我們知道,之所以會(huì)有競(jìng)爭(zhēng)是因?yàn)橛泄蚕碣Y源的存在,多個(gè)線(xiàn)程都想要得到那個(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 指令的線(xiàn)程就是試圖去獲取 monitor 的所有權(quán),搶到了就是成功獲取鎖了。
這個(gè) monitor 下文會(huì)詳細(xì)分析,我們先看下生成的字節(jié)碼是怎樣的。
圖片上方是 lockObject 方法編譯得到的字節(jié)碼,下面就是 lockObject 方法,這樣對(duì)著看比較容易理解。
從截圖來(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)前方法的訪(fǎng)問(wèn)標(biāo)記上做了手腳。
我這里用的是 idea 的插件來(lái)看字節(jié)碼,所以展示的字面結(jié)果不太一樣,不過(guò) flag 標(biāo)記是一樣的:0x0021 ,是 ACC_PUBLIC 和 ACC_SYNCHRONIZED 的結(jié)合。
原理就是修飾方法的時(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,修飾類(lèi)方法就是當(dāng)前類(lèi)(關(guān)于這點(diǎn)是有坑的,我寫(xiě)的這篇文章分析過(guò))。
我還記得有個(gè)面試題,好像是面字節(jié)跳動(dòng)時(shí)候問(wèn)的,面試官問(wèn) synchronized 修飾方法和代碼塊的時(shí)候字節(jié)碼層面有什么區(qū)別?。
怎么說(shuō)?不知不覺(jué)距離字節(jié)跳動(dòng)又更近了呢。
我們?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 上。
我再畫(huà)一下 64 位時(shí) MarkWord 在不同狀態(tài)下的內(nèi)存布局(里面的 monitor 打錯(cuò)了,但是我不準(zhǔn)備改,留個(gè)印記哈哈)。
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)的。
而這個(gè) monitor 在 HotSpot 中是 c++ 實(shí)現(xiàn)的,叫 ObjectMonitor,它是管程的實(shí)現(xiàn),也有叫監(jiān)視器的。
它長(zhǎng)這樣,重點(diǎn)字段我都注釋了含義,還專(zhuān)門(mén)截了個(gè)頭文件的注釋?zhuān)?/p>
暫時(shí)記憶一下,等下源碼和這幾個(gè)字段關(guān)聯(lián)很大。
synchronized 底層原理
先來(lái)一張圖,結(jié)合上面 monitor 的注釋?zhuān)瓤纯?,看不懂沒(méi)關(guān)系,有個(gè)大致流轉(zhuǎn)的印象即可:
好,我們繼續(xù)。
前面我們提到了 monitorenter 這個(gè)指令,這個(gè)指令會(huì)執(zhí)行下面的代碼:
我們現(xiàn)在分析的是重量級(jí)鎖,所以不關(guān)心偏向的代碼,而 slow_enter 方法文章一開(kāi)始的截圖就是了,最終會(huì)執(zhí)行到 ObjectMonitor::enter
這個(gè)方法中。
可以看到重點(diǎn)就是通過(guò) CAS 把 ObjectMonitor 中的 _owner 設(shè)置為當(dāng)前線(xiàn)程,設(shè)置成功就表示獲取鎖成功。
然后通過(guò) recursions 的自增來(lái)表示重入。
如果 CAS 失敗的話(huà),會(huì)執(zhí)行下面的一個(gè)循環(huán):
EnterI 的代碼其實(shí)上面也已經(jīng)截圖了,這里再來(lái)一次,我把重要的入隊(duì)操作加上,并且刪除了一些不重要的代碼:
先再?lài)L試一下獲取鎖,不行的話(huà)就自適應(yīng)自旋,還不行就包裝成 ObjectWaiter 對(duì)象加入到 _cxq 這個(gè)單向鏈表之中,掙扎一下還是沒(méi)搶到鎖的話(huà),那么就要阻塞了,所以下面還有個(gè)阻塞的方法。
可以看到不論哪個(gè)分支都會(huì)執(zhí)行 Self->_ParkEvent->park()
,這個(gè)就是上文提到的調(diào)用 pthread_mutex_lock
。
至此爭(zhēng)搶鎖的流程已經(jīng)很清晰了,我再畫(huà)個(gè)圖來(lái)理一理。
接下來(lái)再看看解鎖的方法
ObjectMonitor::exit
就是解鎖時(shí)會(huì)調(diào)用的方法。
可重入鎖就是根據(jù) _recursions 來(lái)判斷的,重入一次 _recursions++,解鎖一次 _recursions--,如果減到 0 說(shuō)明需要釋放鎖了。
然后此時(shí)解鎖的線(xiàn)程還會(huì)喚醒之前等待的線(xiàn)程,這里有好幾種模式,我們來(lái)看看。
如果 QMode == 2 && _cxq != NULL
的時(shí)候:
如果QMode == 3 && _cxq != NULL
的時(shí)候,我就截取了一部分代碼:
如果 QMode == 4 && _cxq != NULL
的時(shí)候:
如果 QMode 不是 2 的話(huà),最終會(huì)執(zhí)行:
至此,解鎖的流程就完畢了!我再畫(huà)一波流程圖:
接下來(lái)再看看調(diào)用 wait 的方法
沒(méi)啥花頭,就是將當(dāng)前線(xiàn)程加入到 _waitSet 這個(gè)雙向鏈表中,然后再執(zhí)行 ObjectMonitor::exit
方法來(lái)釋放鎖。
接下來(lái)再看看調(diào)用 notify 的方法
也沒(méi)啥花頭,就是從 _waitSet 頭部拿節(jié)點(diǎn),然后根據(jù)策略選擇是放在 cxq 還是 EntryList 的頭部或者尾部,并且進(jìn)行喚醒。
至于 notifyAll 我就不分析了,一樣的,無(wú)非就是做了個(gè)循環(huán),全部喚醒。
至此 synchronized 的幾個(gè)操作都齊活了,出去可以說(shuō)自己深入研究過(guò) synchronized 了。
現(xiàn)在再來(lái)看下這個(gè)圖,應(yīng)該心里很有數(shù)了。
為什么會(huì)有_cxq 和 _EntryList 兩個(gè)列表來(lái)放線(xiàn)程?
因?yàn)闀?huì)有多個(gè)線(xiàn)程會(huì)同時(shí)競(jìng)爭(zhēng)鎖,所以搞了個(gè) _cxq 這個(gè)單向鏈表基于 CAS 來(lái) hold 住這些并發(fā),然后另外搞一個(gè) _EntryList 這個(gè)雙向鏈表,來(lái)在每次喚醒的時(shí)候搬遷一些線(xiàn)程節(jié)點(diǎn),降低 _cxq 的尾部競(jìng)爭(zhēng)。
引入自旋
synchronized 的原理大致應(yīng)該都清晰了,我們也知道了底層會(huì)用到系統(tǒng)調(diào)用,會(huì)有較大的開(kāi)銷(xiāo),那思考一下該如何優(yōu)化?
從小標(biāo)題就已經(jīng)知道了,方案就是自旋,文章開(kāi)頭就已經(jīng)說(shuō)了,這里再提一提。
自旋其實(shí)就是空轉(zhuǎn) CPU,執(zhí)行一些無(wú)意義的指令,目的就是不讓出 CPU 等待鎖的釋放。
正常情況下鎖獲取失敗就應(yīng)該阻塞入隊(duì),但是有時(shí)候可能剛一阻塞,別的線(xiàn)程就釋放鎖了,然后再喚醒剛剛阻塞的線(xiàn)程,這就沒(méi)必要了。
所以在線(xiàn)程競(jìng)爭(zhēng)不是很激烈的時(shí)候,稍微自旋一會(huì)兒,指不定不需要阻塞線(xiàn)程就能直接獲取鎖,這樣就避免了不必要的開(kāi)銷(xiāo),提高了鎖的性能。
但是自旋的次數(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)阻塞的線(xiàn)程。
未競(jìng)爭(zhēng)到鎖的線(xiàn)程存儲(chǔ)到等待隊(duì)列中,獲得鎖的線(xiàn)程調(diào)用 wait 后便存放在條件等待隊(duì)列中,解鎖和 notify 都會(huì)喚醒相應(yīng)隊(duì)列中的等待線(xiàn)程來(lái)爭(zhēng)搶鎖。
然后由于阻塞和喚醒依賴(lài)于底層的操作系統(tǒng)實(shí)現(xiàn),系統(tǒng)調(diào)用存在用戶(hù)態(tài)與內(nèi)核態(tài)之間的切換,所以有較高的開(kāi)銷(xiāo),因此稱(chēng)之為重量級(jí)鎖。
所以又引入了自適應(yīng)自旋機(jī)制,來(lái)提高鎖的性能。
我們?cè)偎伎家幌?,是否有這樣的場(chǎng)景:多個(gè)線(xiàn)程都是在不同的時(shí)間段來(lái)請(qǐng)求同一把鎖,此時(shí)根本就用不需要阻塞線(xiàn)程,連 monitor 對(duì)象都不需要,所以就引入了輕量級(jí)鎖這個(gè)概念,避免了系統(tǒng)調(diào)用,減少了開(kāi)銷(xiāo)。
在鎖競(jìng)爭(zhēng)不激烈的情況下,這種場(chǎng)景還是很常見(jiàn)的,可能是常態(tài),所以輕量級(jí)鎖的引入很有必要。
在介紹輕量級(jí)鎖的原理之前,再看看之前 MarkWord 圖。
輕量級(jí)鎖操作的就是對(duì)象頭的 MarkWord 。
如果判斷當(dāng)前處于無(wú)鎖狀態(tài),會(huì)在當(dāng)前線(xiàn)程棧的當(dāng)前棧幀中劃出一塊叫 LockRecord 的區(qū)域,然后把鎖對(duì)象的 MarkWord 拷貝一份到 LockRecord 中稱(chēng)之為 dhw(就是那個(gè)set_displaced_header 方法執(zhí)行的)里。
然后通過(guò) CAS 把鎖對(duì)象頭指向這個(gè) LockRecord 。
輕量級(jí)鎖的加鎖過(guò)程:
如果當(dāng)前是有鎖狀態(tài),并且是當(dāng)前線(xiàn)程持有的,則將 null 放到 dhw 中,這是重入鎖的邏輯。
我們?cè)倏聪螺p量級(jí)鎖解鎖的邏輯:
邏輯還是很簡(jiǎn)單的,就是要把當(dāng)前棧幀中 LockRecord 存儲(chǔ)的 markword (dhw)通過(guò) CAS 換回到對(duì)象頭中。
如果獲取到的 dhw 是 null 說(shuō)明此時(shí)是重入的,所以直接返回即可,否則就是利用 CAS 換,如果 CAS 失敗說(shuō)明此時(shí)有競(jìng)爭(zhēng),那么就膨脹!
關(guān)于這個(gè)輕量級(jí)加鎖我再多說(shuō)幾句。
每次加鎖肯定是在一個(gè)方法調(diào)用中,而方法調(diào)用就是有棧幀入棧,如果是輕量級(jí)鎖重入的話(huà)那么此時(shí)入棧的棧幀里面的 dhw 就是 null,否則就是鎖對(duì)象的 markword。
這樣在解鎖的時(shí)候就能通過(guò) dhw 的值來(lái)判斷此時(shí)是否是重入的。
我們?cè)偎伎家幌?,是否有這樣的場(chǎng)景:一開(kāi)始一直只有一個(gè)線(xiàn)程持有這個(gè)鎖,也不會(huì)有其他線(xiàn)程來(lái)競(jìng)爭(zhēng),此時(shí)頻繁的 CAS 是沒(méi)有必要的,CAS 也是有開(kāi)銷(xiāo)的。
所以 JVM 研究者們就搞了個(gè)偏向鎖,就是偏向一個(gè)線(xiàn)程,那么這個(gè)線(xiàn)程就可以直接獲得鎖。
我們?cè)倏纯催@個(gè)圖,偏向鎖在第二行。
原理也不難,如果當(dāng)前鎖對(duì)象支持偏向鎖,那么就會(huì)通過(guò) CAS 操作:將當(dāng)前線(xiàn)程的地址(也當(dāng)做唯一ID)記錄到 markword 中,并且將標(biāo)記字段的最后三位設(shè)置為 101。
之后有線(xiàn)程請(qǐng)求這把鎖,只需要判斷 markword 最后三位是否為 101,是否指向的是當(dāng)前線(xiàn)程的地址。
還有一個(gè)可能很多文章會(huì)漏的點(diǎn),就是還需要判斷 epoch 值是否和鎖對(duì)象的類(lèi)中的 epoch 值相同。
如果都滿(mǎn)足,那么說(shuō)明當(dāng)前線(xiàn)程持有該偏向鎖,就可以直接返回。
這 epoch 干啥用的?
可以理解為是第幾代偏向鎖。
偏向鎖在有競(jìng)爭(zhēng)的時(shí)候是要執(zhí)行撤銷(xiāo)操作的,其實(shí)就是要升級(jí)成輕量級(jí)鎖。
而當(dāng)一類(lèi)對(duì)象撤銷(xiāo)的次數(shù)過(guò)多,比如有個(gè) Yes 類(lèi)的對(duì)象作為偏向鎖,經(jīng)常被撤銷(xiāo),次數(shù)到了一定閾值(XX:BiasedLockingBulkRebiasThreshold,默認(rèn)為 20 )就會(huì)把當(dāng)代的偏向鎖廢棄,把類(lèi)的 epoch 加一。
所以當(dāng)類(lèi)對(duì)象和鎖對(duì)象的 epoch 值不等的時(shí)候,當(dāng)前線(xiàn)程可以將該鎖重偏向至自己,因?yàn)榍耙淮蜴i已經(jīng)廢棄了。
不過(guò)為保證正在執(zhí)行的持有鎖的線(xiàn)程不能因?yàn)檫@個(gè)而丟失了鎖,偏向鎖撤銷(xiāo)需要所有線(xiàn)程處于安全點(diǎn),然后遍歷所有線(xiàn)程的 Java 棧,找出該類(lèi)已加鎖的實(shí)例,并且將它們標(biāo)記字段中的 epoch 值加 1。
當(dāng)撤銷(xiāo)次數(shù)超過(guò)另一個(gè)閾值(XX:BiasedLockingBulkRevokeThreshold,默認(rèn)值為 40),則廢棄此類(lèi)的偏向功能,也就是說(shuō)這個(gè)類(lèi)都無(wú)法偏向了。
至此整個(gè) Synchronized 的流程應(yīng)該都比較清楚了。
我是反著來(lái)講鎖升級(jí)的過(guò)程的,因?yàn)槭聦?shí)上是先有的重量級(jí)鎖,然后根據(jù)實(shí)際分析優(yōu)化得到的偏向鎖和輕量級(jí)鎖。
包括期間的一些細(xì)節(jié)應(yīng)該也較為清楚了,我覺(jué)得對(duì)于 Synchronized 了解到這份上差不多了。
我再搞了張 openjdk wiki 上的圖,看看是不是很清晰了:
“Synchronized的輕量級(jí)鎖是否自旋”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!