synchronized 底層原理總結

本文總結下Synchronized關鍵字的底層實現原理。

一.synchronized介紹

synchronized是JVM內置鎖,通過內部對象Monitor(監視器鎖)來實現,基於進入與退出monitor對象來實現方法與代碼塊的同步,監視器鎖的實現,最終依賴操作系統的Mutex lock(互斥鎖)來實現。
在這裏插入圖片描述

二.synchronized使用方式

synchronized 主要有3種使用方式。

1.同步類方法

public synchronized void method()
{
   // todo
}

鎖的是當前類對象;

2.同步代碼塊

public class TestNotes {
	private static Object object;
	
    public String decStock() {
        synchronized (object) {
            //todo
        }
        return "下單成功";
    }
}

或者

public  void run() {
   synchronized(this) {
      //todo
   }
}

鎖的是括號裏面的對象;

3.修飾一個類

lass ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

三.java對象組成

在這裏插入圖片描述
java對象由3部分組成:

  • 對象頭 :比較複雜,synchronized加的鎖,就保存在這裏面的某些數據位。對象頭大小是固定的;
  • 實例數據 :就是對象的業務數據;
  • 對齊填充位 :64位jvm,默認需要對象大小必須位8byte(字節)的整數倍,所以有時候需要對齊填充位。

Mark Word

其中,鎖的不同狀態,就是存在對象頭中的Mark Word區域中,
在這裏插入圖片描述
下圖是32位系統的Mark Word的區域具體分佈:
在這裏插入圖片描述

四.鎖的升級過程

synchronized鎖有如下4種狀態:

  • 無鎖,不鎖住資源,多個線程只有一個能修改資源成功,其他線程會重試;
  • 偏向鎖,同一個線程獲取同步資源時,沒有別人競爭時,去掉所有同步操作,相當於沒鎖;
  • 輕量級鎖,多個線程搶奪同步資源時,沒有獲得鎖的線程使用CAS自旋等待鎖的釋放;
  • 重量級鎖,多個線程搶奪同步資源時,使用操作系統的互斥量進行同步,沒有獲得鎖的線程阻塞等待喚醒;

鎖的升級過程:無鎖-》偏向鎖-》輕量級鎖-》重量級鎖。注意,升級並不一定是一級級升的,有可能跨級別,比如由無鎖狀態,直接升級爲輕量級鎖。

下面,我們以這段代碼爲例,分析以下鎖的具體升級過程:

public class TestNotes {
	private static Object object;
	//減庫存
    public String decStock() {
        synchronized (object) {
            //todo
        }
        return "下單成功";
    }
}

這段代碼中,synchronized鎖的是object實例對象,鎖的具體狀態,就保存在object對象頭部的markword區域。

1. 無鎖狀態:

在這裏插入圖片描述
步驟說明:
1.synchronized鎖的object對象頭部markword區域,最開始鎖狀態標誌位,默認值就是001,也就是無鎖狀態。
在這裏插入圖片描述

2. 偏向鎖狀態:

某刻,線程1執行到同步代碼塊,虛擬機會使用CAS嘗試修改狀態標誌位,修改爲偏向鎖狀態,並且把線程1的線程ID記錄到markword區域的23bit位,進入偏向鎖狀態,如下圖:
在這裏插入圖片描述
進入偏向鎖狀態後,如果沒有其他線程競爭,線程1後續再次訪問同步代碼塊時,猶如沒有鎖一樣,jvm不會再進行CAS加鎖、解鎖等步驟,直接運行同步塊代碼。直到線程1執行完畢後,jvm會釋放偏向鎖,將markword的標識位恢復初始狀態。

3. 輕量級鎖狀態:

線程1在持有偏向鎖期間,線程2來了,下圖右側部分是線程2執行過程:
在這裏插入圖片描述
線程2訪問同步代碼塊,嘗試獲取鎖;此時jvm會檢查線程1的狀態,因爲線程1還持有鎖,jvm不能撤銷線程1的鎖,此時,jvm就會把鎖升級位輕量級鎖,也就是這個23bit區域存了線程1的地址,指向線程1的線程棧中的某塊區域;同時線程棧的這塊內存也保存了指向markword的引用,相當於兩塊區域互換了內容。

上文中描述的過程,是由無鎖,然後變爲偏向鎖,然後是輕量級鎖;
但是有些場景,鎖會直接由無鎖升級爲輕量級鎖,比如下圖過程:
在這裏插入圖片描述
上圖中,某一時刻,同時有兩個線程執行到同步代碼塊,但實際肯定只能有一個線程先進入,假如是線程1,那麼此時就會直接進輕量級鎖狀態。

此時,線程2就會進行CAS自旋,尋找機會獲取輕量級鎖,如下圖:
在這裏插入圖片描述

4. 重量級鎖狀態:

上圖中,進入輕量級鎖狀態後,線程2還會繼續自旋嘗試獲取鎖;這個時候,synchronized並不會立即進入重量級鎖狀態,而是等到線程2 自旋達到一定次數後,jvm才膨脹爲下圖的重量級鎖。這個自旋次數,jdk7及以後可以通過jvm參數設置。
在這裏插入圖片描述
線程1執行完同步代碼塊,jvm嘗試釋放鎖,修改markword爲初始的無鎖狀態,在釋放鎖的時候,發現已經是重量鎖了,說明有其他線程競爭,並且其他線程肯定已經進入了阻塞狀態,那麼jvm在釋放鎖之後,還會喚醒其他進入阻塞狀態的線程。

五.總結

synchronized在jdk 1.6版本進行了優化,性能有了巨大提升,基本上和java鎖性能沒有什麼差異,所以在生產環境中,synchronized能滿足的場景,儘量使用synchronized,簡單方便。

優化的關鍵,是加入了輕量級鎖,使用CAS,還有自適應機制,避免了向底層操作系統申請互斥量,避免了用戶態和內核態的切換,也就是在一定程度上避免了線程上下文的切換,暫時不進入重量級鎖的狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章