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