面試突擊39:synchronized底層是如何實現的?

想了解 synchronized 是如何運行的?就要先搞清楚 synchronized 是如何實現?
synchronized 同步鎖是通過 JVM 內置的 Monitor 監視器實現的,而監視器又是依賴作業系統的互斥鎖 Mutex 實現的,那接下來我們先來了解一下監視器。

監視器

監視器是一個概念或者說是一個機制,它用來保障在任何時候,只有一個執行緒能夠執行指定區域的程式碼。

一個監視器像是一個建築,建築里有一個特殊的房間,這個房間同一時刻只能被一個執行緒所佔有。一個執行緒從進入該房間到離開該房間,可以全程獨佔該房間的所有數據。進入該建築叫做進入監視器(entering the monitor),進入該房間叫做獲得監視器(acquiring the monitor),獨自佔有該房間叫做擁有監視器(owning the monitor),離開該房間叫做釋放監視器(releasing the monitor),離開該建築叫做退出監視器(exiting the monitor)。

嚴格意義來說監視器和鎖的概念是不同的,但很多地方也把二者相互指代。

底層實現

下面我們在程式碼中添加一個 synchronized 程式碼塊,來觀察一下它在位元組碼層面是如何實現的?示例程式碼如下:

public class SynchronizedToMonitorExample {
    public static void main(String[] args) {
        int count = 0;
        synchronized (SynchronizedToMonitorExample.class) {
            for (int i = 0; i < 10; i++) {
                count++;
            }
        }
        System.out.println(count);
    }
}

當我們將上述程式碼編譯成位元組碼之後,得到的結果是這樣的:

從上述結果我們可以看出,在 main 方法中多了一對 monitorenter 和 monitorexit 的指令,它們的含義是:

  • monitorenter:表示進入監視器。
  • monitorexit:表示退出監視器。

由此可知 synchronized 是依賴 Monitor 監視器實現的。

執行流程

在 Java 中,synchronized 是非公平鎖,也是可以重入鎖
所謂的非公平鎖是指,執行緒獲取鎖的順序不是按照訪問的順序先來先到的,而是由執行緒自己競爭,隨機獲取到鎖。
可重入鎖指的是,一個執行緒獲取到鎖之後,可以重複得到該鎖。這些內容是理解接下來內容的前置知識。
在 HotSpot 虛擬機中,Monitor 底層是由 C++實現的,它的實現對象是 ObjectMonitor,ObjectMonitor 結構體的實現如下:

ObjectMonitor::ObjectMonitor() {  
  _header       = NULL;  
  _count       = 0;  
  _waiters      = 0,  
  _recursions   = 0;       //執行緒的重入次數
  _object       = NULL;  
  _owner        = NULL;    //標識擁有該monitor的執行緒
  _WaitSet      = NULL;    //等待執行緒組成的雙向循環鏈表,_WaitSet是第一個節點
  _WaitSetLock  = 0 ;  
  _Responsible  = NULL ;  
  _succ         = NULL ;  
  _cxq          = NULL ;    //多執行緒競爭鎖進入時的單向鏈表
  FreeNext      = NULL ;  
  _EntryList    = NULL ;    //_owner從該雙向循環鏈表中喚醒執行緒結點,_EntryList是第一個節點
  _SpinFreq     = 0 ;  
  _SpinClock    = 0 ;  
  OwnerIsThread = 0 ;  
} 

在以上程式碼中有幾個關鍵的屬性:

  • _count:記錄該執行緒獲取鎖的次數(也就是前前後後,這個執行緒一共獲取此鎖多少次)。
  • _recursions:鎖的重入次數。
  • _owner:The Owner 擁有者,是持有該 ObjectMonitor(監視器)對象的執行緒;
  • _EntryList:EntryList 監控集合,存放的是處於阻塞狀態的執行緒隊列,在多執行緒下,競爭失敗的執行緒會進入 EntryList 隊列。
  • _WaitSet:WaitSet 待授權集合,存放的是處於 wait 狀態的執行緒隊列,當執行緒執行了 wait() 方法之後,會進入 WaitSet 隊列。

監視器執行的流程如下:

  1. 執行緒通過 CAS(對比並替換)嘗試獲取鎖,如果獲取成功,就將 _owner 欄位設置為當前執行緒,說明當前執行緒已經持有鎖,並將 _recursions 重入次數的屬性 +1。如果獲取失敗則先通過自旋 CAS 嘗試獲取鎖,如果還是失敗則將當前執行緒放入到 EntryList 監控隊列(阻塞)。
  2. 當擁有鎖的執行緒執行了 wait 方法之後,執行緒釋放鎖,將 owner 變數恢復為 null 狀態,同時將該執行緒放入 WaitSet 待授權隊列中等待被喚醒。
  3. 當調用 notify 方法時,隨機喚醒 WaitSet 隊列中的某一個執行緒,當調用 notifyAll 時喚醒所有的 WaitSet 中的執行緒嘗試獲取鎖。
  4. 執行緒執行完釋放了鎖之後,會喚醒 EntryList 中的所有執行緒嘗試獲取鎖。

以上就是監視器的執行流程,執行流程如下圖所示:
image.png

總結

synchronized 同步鎖是通過 JVM 內置的 Monitor 監視器實現的,而監視器又是依賴作業系統的互斥鎖 Mutex 實現的。JVM 監視器的執行流程是:執行緒先通過自旋 CAS 的方式嘗試獲取鎖,如果獲取失敗就進入 EntrySet 集合,如果獲取成功就擁有該鎖。當調用 wait() 方法時,執行緒釋放鎖並進入 WaitSet 集合,等其他執行緒調用 notify 或 notifyAll 方法時再嘗試獲取鎖。鎖使用完之後就會通知 EntrySet 集合中的執行緒,讓它們嘗試獲取鎖。

參考資料

www.cnblogs.com/freelancy/p/15625602.html

blog.csdn.net/qq_43783527/article/details/114669174

www.cnblogs.com/hongdada/p/14513036.html

是非審之於己,毀譽聽之於人,得失安之於數。

公眾號:Java面試真題解析

面試合集://gitee.com/mydb/interview