在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...
本篇文章主要從字節(jié)碼和JVM底層來分析synchronized實(shí)現(xiàn)原理和鎖升級過程,其中涉及到了簡單認(rèn)識(shí)字節(jié)碼、對象內(nèi)部結(jié)構(gòu)以及ObjectMonitor等知識(shí)點(diǎn)。
閱讀本文之前,如果大家對synchronized關(guān)鍵字的基本使用還不是很了解的話,推薦閱讀筆者之前的一遍關(guān)于synchronized關(guān)鍵字使用的文章:
synchronized三種使用方式都不知道還想通過面試,門都沒有
從字節(jié)碼角度分析synchronized實(shí)現(xiàn)
從JVM規(guī)范中可以了解到,無論是synchronized修飾方法(實(shí)例/靜態(tài)方法)還是代碼塊都是基于進(jìn)入(entry)和退出(exit)monitor對象來實(shí)現(xiàn),但是兩種修飾方式在字節(jié)碼層面實(shí)現(xiàn)上有著很大區(qū)別。下面我們通過javap -verbose XXX.class命令查看class文件信息來具體分析兩者實(shí)現(xiàn)上的差異。
synchronized修飾代碼塊:
程序源碼如下:
源碼截圖
class文件信息如下:
class文件信息截圖
由上面的class信息可以得知,使用synchronized修飾代碼塊會(huì)在同步代碼塊之前加monitorenter指令,同時(shí)在代碼塊正常退出(15行)和異常退出(21行)的地方插入monitorexit指令,從而保證monitorenter和monitorexit的成對執(zhí)行(保證同步代碼塊執(zhí)行結(jié)束的同時(shí)釋放鎖資源)。可以把monitorenter看作lock.lock(),monitorexit看作lock.unlock(),那么monitorenter和monitorexit可以用更加方便理解的偽代碼表示,如下:
偽代碼截圖
synchronized修飾方法:
程序源碼如下:
源碼截圖
class文件信息如下:
class文件信息截圖
由上面的class信息可以得知,synchronized修飾方法并沒有通過插入monitorentry和monitorexit指令來實(shí)現(xiàn),而是在方法表結(jié)構(gòu)中的訪問標(biāo)志(access_flags)設(shè)置ACC_SYNCHRONIZED標(biāo)志來實(shí)現(xiàn)。線程在執(zhí)行方法前先判斷access_flags是否標(biāo)記ACC_SYNCHRONIZED,如果標(biāo)記則在執(zhí)行方法前先去獲取monitor對象,獲取成功則執(zhí)行方法代碼且執(zhí)行完畢后釋放monitor對象,獲取失敗則表示monitor對象被其他線程獲取從而阻塞當(dāng)前線程。
對象頭和MarkWord
說到對象頭,我們需要先整體了解下對象的內(nèi)部結(jié)構(gòu),如下圖所示:
對象內(nèi)部結(jié)構(gòu)圖
由圖可知對象內(nèi)部結(jié)構(gòu)分為:對象頭、實(shí)例數(shù)據(jù)、對齊填充(保證8個(gè)字節(jié)的倍數(shù))。對象頭分為對象標(biāo)記(markOop)和類元信息(klassOop)。類元信息存儲(chǔ)的是指向該對象類元數(shù)據(jù)(klass)的首地址,4個(gè)字節(jié)。
對象標(biāo)記(markOop)是我們重要要介紹的,它存儲(chǔ)對象本身運(yùn)行時(shí)的數(shù)據(jù),如哈希碼、GC標(biāo)記、鎖信息、線程關(guān)聯(lián)等(64位JVM占8個(gè)字節(jié),32位JVM占4個(gè)字節(jié)),稱為"Mark Word",存儲(chǔ)格式非規(guī)定與具體JVM實(shí)現(xiàn)有關(guān)。
Hotspot JVM中MarkWord存儲(chǔ)格式如下:
32位存儲(chǔ)格式:
32位存儲(chǔ)格式
64位存儲(chǔ)格式:
64位存儲(chǔ)格式
由MarkWord存儲(chǔ)格式可以了解到JVM可以通過鎖標(biāo)志位來判斷鎖類型,進(jìn)而進(jìn)行處理。注意JDK1.6之前只有重量級鎖的,JDK1.6之后才有了偏向鎖和輕量級鎖,后面鎖升級部分會(huì)詳細(xì)講解。
ObjectMonitor
在JVM的規(guī)范中,有這么一些話:“在JVM中,每個(gè)對象和類在邏輯上都是和一個(gè)監(jiān)視器相關(guān)聯(lián)的,為了實(shí)現(xiàn)監(jiān)視器的排他性監(jiān)視能力,JVM為每一個(gè)對象和類都關(guān)聯(lián)一個(gè)鎖,鎖住了一個(gè)對象,就是獲得對象相關(guān)聯(lián)的監(jiān)視器”。這里的監(jiān)視器就是指的是ObjectMonitor。
ObjectMonitor在JVM源碼中的定義如下:
ObjectMonitor的JVM源碼
MarkWord中重量級鎖指向的重量級指針就是ObjectMonitor對象指針,是基于操作系統(tǒng)互斥(mutex)實(shí)現(xiàn)的。
synchronized鎖升級和實(shí)現(xiàn)原理
synchronized在修飾方法和代碼塊在字節(jié)碼上實(shí)現(xiàn)方式有很大差異,但是內(nèi)部實(shí)現(xiàn)還是基于對象頭的MarkWord來實(shí)現(xiàn)的。JDK1.6之前synchronized使用的是重量級鎖,JDK1.6之后進(jìn)行了優(yōu)化,擁有了無鎖->偏向鎖->輕量級鎖->重量級鎖的升級過程,而不是無論什么情況都使用重量級鎖。
synchronized涉及的鎖歸類
鎖升級的優(yōu)化是針對于不同同步場景進(jìn)行的優(yōu)化,在不存在鎖競爭的時(shí)候進(jìn)入同步方法/代碼塊則使用偏向鎖,存在競爭時(shí)升級為輕量級鎖,輕量級鎖采用的是自旋鎖,如果同步方法/代碼塊執(zhí)行時(shí)間很短的話,采用輕量級鎖雖然會(huì)占用cpu資源但是相對比使用重量級鎖還是更高效的,但是如果同步方法/代碼塊執(zhí)行時(shí)間很長,那么使用輕量級鎖自旋帶來的性能消耗就比使用重量級鎖更嚴(yán)重,這時(shí)候就需要升級為重量級鎖。
MarkWord結(jié)構(gòu)和鎖特征
下面結(jié)合上圖所示的MarkWord對幾種鎖類型進(jìn)行介紹:
- 無鎖:MarkWord標(biāo)志位01,沒有線程執(zhí)行同步方法/代碼塊時(shí)的狀態(tài)。
- 偏向鎖:MarkWord標(biāo)志位01(和無鎖標(biāo)志位一樣)。偏向鎖是通過在bitfields中通過CAS設(shè)置當(dāng)前正在執(zhí)行的ThreadID來實(shí)現(xiàn)的。假設(shè)線程A獲取偏向鎖執(zhí)行代碼塊(即對象頭設(shè)置了ThreadA_ID),線程A同步塊未執(zhí)行結(jié)束時(shí),線程B通過CAS嘗試設(shè)置ThreadB_ID會(huì)失敗,因?yàn)榇嬖阪i競爭情況,這時(shí)候就需要升級為輕量級鎖。注:偏向鎖是針對于不存在資源搶占情況時(shí)候使用的鎖,如果被synchronized修飾的方法/代碼塊競爭線程多可以通過禁用偏向鎖來減少一步鎖升級過程??梢酝ㄟ^JVM參數(shù)-XX:-UseBiasedLocking = false來關(guān)閉偏向鎖。
- 輕量級鎖:MarkWord標(biāo)志位00。輕量級鎖是采用自旋鎖的方式來實(shí)現(xiàn)的,自旋鎖分為固定次數(shù)自旋鎖和自適應(yīng)自旋鎖。 輕量級鎖是針對競爭鎖對象線程不多且線程持有鎖時(shí)間不長的場景, 因?yàn)樽枞€程需要CPU從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),代價(jià)很大,如果一個(gè)剛剛阻塞不久就被釋放代價(jià)有大。具體實(shí)現(xiàn)和升級為重量級鎖過程:線程A獲取輕量級鎖時(shí)會(huì)把對象頭中的MarkWord復(fù)制一份到線程A的棧幀中創(chuàng)建用于存儲(chǔ)鎖記錄的空間DisplacedMarkWord,然后使用CAS將對象頭中的內(nèi)容替換成線程A存儲(chǔ)DisplacedMarkWord的地址。如果這時(shí)候出現(xiàn)線程B來獲取鎖,線程B也跟線程A同樣復(fù)制對象頭的MarkWord到自己的DisplacedMarkWord中,如果線程A鎖還沒釋放,這時(shí)候那么線程B的CAS操作會(huì)失敗,會(huì)繼續(xù)自旋,當(dāng)然不可能讓線程B一直自旋下去,自旋到一定次數(shù)(固定次數(shù)/自適應(yīng))就會(huì)升級為重量級鎖。
- 重量級鎖:通過對象內(nèi)部監(jiān)視器(monitor)實(shí)現(xiàn),monitor本質(zhì)前面也提到了是基于操作系統(tǒng)互斥(mutex)實(shí)現(xiàn)的,操作系統(tǒng)實(shí)現(xiàn)線程之間切換需要從用戶態(tài)到內(nèi)核態(tài)切換,成本非常高。
注:鎖只可以升級不可以降級,但是偏向鎖可以被重置為無鎖狀態(tài)。
最后,附上一張關(guān)于synchronized鎖升級流程圖(很全面很牛):
作者收藏很久的,synchronized鎖升級流程圖(很全面很牛)
注:由于文章中上傳的圖片會(huì)被壓縮,清晰度受到影響,可以關(guān)注并私信作者"鎖升級"獲取synchronized鎖升級流程圖(高清版)。
END
筆者是一位熱愛互聯(lián)網(wǎng)、熱愛互聯(lián)網(wǎng)技術(shù)、熱于分享的年輕人,如果您跟我一樣,我愿意成為您的朋友,分享每一個(gè)有價(jià)值的知識(shí)給您。喜歡作者的同學(xué),點(diǎn)贊+轉(zhuǎn)發(fā)+關(guān)注哦!
點(diǎn)贊+轉(zhuǎn)發(fā)+關(guān)注,私信作者“讀書筆記”即可獲得BAT大廠面試資料、高級架構(gòu)師VIP視頻課程等高質(zhì)量技術(shù)資料。
BAT等一線互聯(lián)網(wǎng)面試資料和VIP高級架構(gòu)師視頻
總結(jié)
以上是生活随笔為你收集整理的在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: notepad python设置_Not
- 下一篇: 用python解决生活问题_Python