1. 程式人生 > >synchronized原理分析

synchronized原理分析

synchronized

synchronized可以保證方法或者程式碼塊在執行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變數的記憶體可見性

synchronized同步的基礎

  1. 普通同步方法,鎖是當前例項物件
  2. 靜態同步方法,鎖是當前類的class物件
  3. 同步方法塊,鎖是括號裡面的物件
public class SynchronizedTest {
	// 普通同步方法
    public synchronized void test1(){

    }
    //靜態同步方法
    public static
synchronized void test1(){ } //同步方法塊 public void test2(){ synchronized (this){ } } }

原理分析基礎

1.鎖原理

java中每個物件都可作為鎖,鎖有四種級別,按照量級從輕到重分為:無鎖、偏向鎖、輕量級鎖、重量級鎖。每個物件一開始都是無鎖的,隨著執行緒間爭奪鎖,越激烈,鎖的級別越高,並且鎖只能升級不能降級

  • 偏向鎖:只有一個執行緒進入臨界區
  • 輕量級鎖:多個執行緒交替進入臨界區
  • 重量級鎖:多個執行緒同時進入臨界區

2.類結構

HotSpot虛擬機器中,物件在記憶體中儲存的佈局可以分為3塊區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)
下圖是普通物件例項與陣列物件例項的資料結構:
在這裡插入圖片描述
1.物件頭

  1. markword :儲存 雜湊碼,GC分代年齡,鎖狀態標識,執行緒持有的鎖,偏向執行緒id,偏向執行緒時間戳
  2. klass:klass型別指標,用於jvm確定該物件為哪個類例項
  3. 陣列長度:物件陣列的陣列長度

2.例項資料

  1. 儲存物件儲存的有效資訊

3.對齊填充

  1. 佔位符的作用

3.理解鎖的基礎知識

3.1巨集觀鎖型別

  1. 樂觀鎖:主要用於讀多寫少的場景,每次去拿資料的時候都認為別人不會修改,所以不會上鎖。但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重複讀-比較-寫的操作。java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗
  2. 悲觀鎖:主要用於寫少讀多的場景,每次去拿資料的時候都認為別人會修改,所以每次在讀寫資料的時候都會上鎖,這樣別人想讀寫這個資料就會block直到拿到鎖。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嚐試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如ReenTranLock

3.2java執行緒阻塞的代價

java的執行緒是對映到作業系統原生執行緒之上的。

  • 每次進行阻塞或者喚醒執行緒都需要作業系統的介入
  • 需要在戶態和核心態之間的切換(使用者態與核心態都有各自專用的記憶體空間,專用的暫存器等)

當執行緒狀態切換高頻時,會消耗很多cpu處理的時間
synchronized會導致爭用不到鎖的執行緒進入阻塞狀態,被稱為重量級鎖,為了緩解上述效能問題,JVM從1.5開始,引入了輕量鎖與偏向鎖,預設啟用了自旋鎖,他們都屬於樂觀鎖。

3.3markword

在這裡插入圖片描述

3.原理分析

在這裡插入圖片描述
synchronized的執行過程:

  1. 檢測Mark Word裡面是不是當前執行緒的ID,如果是,表示當前執行緒處於偏向鎖
  2. 如果不是,則使用CAS將當前執行緒的ID替換Mard Word,如果成功則表示當前執行緒獲得偏向鎖,置偏向標誌位1
  3. 如果失敗,則說明發生競爭,撤銷偏向鎖,進而升級為輕量級鎖。
  4. 當前執行緒使用CAS將物件頭的Mark Word替換為鎖記錄指標,如果成功,當前執行緒獲得鎖
  5. 如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。
  6. 如果自旋成功則依然處於輕量級狀態。
  7. 如果自旋失敗,則升級為重量級鎖。

synchronized 在jdk1.5中入了輕量鎖與偏向鎖,預設啟用了自旋鎖,減少了重量級鎖的運用。
在這裡插入圖片描述
1.Contention List:競爭佇列,所有請求鎖的執行緒首先被放在這個競爭佇列中;
2.Entry List:Contention List中那些有資格成為候選資源的執行緒被移動到Entry List中;
3.Wait Set:哪些呼叫wait方法被阻塞的執行緒被放置在這裡;
4.OnDeck:任意時刻,最多隻有一個執行緒正在競爭鎖資源,該執行緒被成為OnDeck;
5.Owner:當前已經獲取到所資源的執行緒被稱為Owner;

3.1偏向鎖

引入偏向鎖主要目的是:為了在無多執行緒競爭的情況下儘量減少不必要的輕量級鎖執行路徑。上面提到了輕量級鎖的加鎖解鎖操作是需要依賴多次CAS原子指令的。那麼偏向鎖是如何來減少不必要的CAS操作呢?我們可以檢視Mark work的結構就明白了。只需要檢查是否為偏向鎖、鎖標識為以及ThreadID即可,處理流程如下:
獲取鎖

  1. 檢測Mark Word是否為可偏向狀態,即是否為偏向鎖1,鎖標識位為01;
  2. 若為可偏向狀態,則測試執行緒ID是否為當前執行緒ID,如果是,則執行步驟(5),否則執行步驟(3);
  3. 如果執行緒ID不為當前執行緒ID,則通過CAS操作競爭鎖,競爭成功,則將Mark Word的執行緒ID替換為當前執行緒ID,否則執行執行緒(4);
  4. 通過CAS競爭鎖失敗,證明當前存在多執行緒競爭情況,當到達全域性安全點,獲得偏向鎖的執行緒被掛起,偏向鎖升級為輕量級鎖,然後被阻塞在安全點的執行緒繼續往下執行同步程式碼塊;
  5. 執行同步程式碼塊

釋放鎖
偏向鎖的釋放採用了一種只有競爭才會釋放鎖的機制,執行緒是不會主動去釋放偏向鎖,需要等待其他執行緒來競爭。偏向鎖的撤銷需要等待全域性安全點(這個時間點是上沒有正在執行的程式碼)。其步驟如下:

  1. 暫停擁有偏向鎖的執行緒,判斷鎖物件石是否還處於被鎖定狀態;
  2. 撤銷偏向蘇,恢復到無鎖狀態(01)或者輕量級鎖的狀態;

3.2輕量級鎖

輕量級鎖是由偏向所升級來的,兩個執行緒交替呼叫方法,偏向鎖就會升級為輕量級鎖;

輕量級鎖的加鎖過程:

  1. 在程式碼進入同步塊的時候,如果同步物件鎖狀態為無鎖狀態(鎖標誌位為“01”狀態,是否為偏向鎖為“0”),虛擬機器首先將在當前執行緒的棧幀中建立一個名為鎖記錄(LockRecord)的空間,用於儲存鎖物件目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。
  2. 拷貝物件頭中的Mark Word複製到鎖記錄中;
  3. 拷貝成功後,虛擬機器將使用CAS操作嘗試將物件的Mark Word更新為指向Lock Record的指標,並將Lock record裡的owner指標指向object mark word。如果更新成功,則執行步驟4,否則執行步驟5。
  4. 如果這個更新動作成功了,那麼這個執行緒就擁有了該物件的鎖,並且物件MarkWord的鎖標誌位設定為“00”,即表示此物件處於輕量級鎖定狀態
  5. 如果這個更新操作失敗了,虛擬機器首先會檢查物件的MarkWord是否指向當前執行緒的棧幀,如果是就說明當前執行緒已經擁有了這個物件的鎖,那就可以直接進入同步塊繼續執行。否則說明多個執行緒競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標誌的狀態值變為“10”,MarkWord中儲存的就是指向重量級鎖(互斥量)的指標,後面等待鎖的執行緒也要進入阻塞狀態。而當前執行緒便嘗試使用自旋來獲取鎖,自旋就是為了不讓執行緒阻塞,而採用迴圈去獲取鎖的過程。

3.3重量級鎖

重量級鎖通過物件內部的監視器(monitor)實現,其中monitor的本質是依賴於底層作業系統的Mutex Lock實現,作業系統實現執行緒之間的切換需要從使用者態到核心態的切換,切換成本非常高。