天天看點

Synchronized的底層實作原理

Synchronized的底層實作原理

如果我們要想了解synchronized底層實作原理,那麼不妨先來了解了解對象在堆中的存儲結構

java對象在堆中的存儲結構分為三個部分,分别為對象頭、執行個體變量和填充位元組。

  • 對象頭裡面主要包含MarkWord和Klass Point類型指針,MarkWord裡面存儲的是對象自身的運作時資料(包含了Hashcode、GC分帶年齡、鎖狀态标記、線程持有的鎖等等),而Klass Point則指向的是類中繼資料的指針,JVM就是通過此指針來确定這個對象是哪個類的執行個體。
  • 執行個體變量裡面存儲的是對象的屬性資訊,包括父類的屬性資訊,按照4個位元組對齊
  • 填充位元組則是因為java虛拟機要求對象位元組數必須是8的整數倍,填充位元組就是用于湊齊這個倍數用的。

    在JVM中,synchronized的對象鎖,其指針指向的是一個monitor對象(由C++實作的ObjectMonitor對象)的起始位址。它的大緻資料結構如下:

    ObjectMonitor() {
        _count        = 0; //用來記錄該對象被線程擷取鎖的次數
        _waiters      = 0;
        _recursions   = 0; //鎖的重入次數
        _owner        = NULL; //指向持有ObjectMonitor對象的線程 
        _WaitSet      = NULL; //處于wait狀态的線程,會被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _EntryList    = NULL ; //處于等待鎖block狀态的線程,會被加入到該清單
      }
               

我們知道線程的生命周期分為5個狀态,分别為start,runing,waiting、blocking、dead

當synchronized修飾方法和代碼塊時,流程如下:

  • 當多個線程同時通路該方法時候,這些線程會先進入_EntryList隊列,此時這些線程處于blocking狀态
  • 當某一個線程擷取到了執行個體對象的螢幕(monitor)鎖,那麼就進入running狀态,執行線程的方法,此時ObjectMonitor對象的_owner指向目前線程,_count加1表示目前對象被一個線程擷取
  • 當running狀态的線程調用wait()方法的時候,那麼目前線程釋放monitor對象,進入waiting狀态,ObjectMonitor對象的_owner置為null,_count減1,同時該線程進入_WaitSet隊列,直到有線程調用notify或者notifyAll方法喚醒該線程,則該線程重新擷取到monitor對象進入running狀态
  • 如果目前線程執行完畢,那麼也釋放monitor對象,進入waiting狀态,ObjectMonitor對象的_owner置為null,_count減1

    那麼synchronized又是如何獲得monitor對象呢?

  • synchronized如果修飾的是代碼塊,則是在代碼塊開始的位置插入monitorenter指令,在同步結束的位置或者異常出現的位置插入monitorexit指令;JVM要保證monitorenter和monitorexit成對出現,故monitorexit出現兩次,防止異常導緻無法配對,我們檢視位元組碼會發現其代碼如下:
    Synchronized的底層實作原理
  • synchronized如果修飾的是方法,則不是通過插入monitorenter和monitorexit指令實作的,而是通過設定方法表結構中的ACC_SYNCHRONIZED辨別,方法在執行時,會讀取這個辨別,如果發現有此辨別則會先去擷取對象的monitor對象,如果擷取成功則執行方法代碼,執行完畢則釋放monitor對象,所謂的擷取monitor對象和釋放monitor對象指的就是操作monitor對象的_owner和_count屬性,我們檢視位元組碼會發現其代碼如下:
    Synchronized的底層實作原理

繼續閱讀