【併發編程】synchronized實現

目錄

1.功能

2.鎖對象

3.JVM實現

3.1 Java對象頭

3.1.1 Mark Word

3.1.2 Klass Word

3.2 鎖狀態

3.2.1 偏向鎖 01

3.2.2 輕量級鎖 00

3.2.3 重量級鎖


1.功能

synchronized是用與線程同步,是一個重量級互斥鎖,可以保證方法或者代碼塊在運行時,同一時刻只有一個線程可以進入到該對象臨界區(同一個對象的所有synchronized保衛的代碼塊)。

synchronized是可重入的,即同一個線程,可以多次進入同個對象的多個synchronized塊。

2.鎖對象

synchronized修飾位置 鎖對象 樣例
實例方法 當前實例對象 this public synchronized void synMethod()
靜態方法 當前類的Class對象 public static synchronized void doSth()
代碼塊

括號裏面的對象,

既可以是實例對象

也可以是類對象

synchronized (lock) {...}
synchronized (lock.getClass()) {...}

3.JVM實現

下面看下一個同步塊編譯後的虛擬機指令是什麼樣的,

方法 .class類文件指令
public void synBlock(){

      synchronized (this) { // 此處自動加鎖

     if (this.x < 12) {
        this.x = 12;
     }
  } // 此處自動解鎖

}

 public void synBlock();

    descriptor: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=3, args_size=1

         0: aload_0

         1: dup

         2: astore_1

         3: monitorenter

         4: aload_0

         5: getfield      #2                  // Field x:I

         8: bipush        12

        10: if_icmpge     19

        13: aload_0

        14: bipush        12

        16: putfield      #2                  // Field x:I

        19: aload_1

        20: monitorexit

        21: goto          29

        24: astore_2

        25: aload_1

        26: monitorexit

        27: aload_2

        28: athrow

        29: return

      Exception table:

         from    to  target type

             4    21    24   any...

      LineNumberTable:

        line 15: 0

       ....

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0      30     0  this   ....

synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指向同步代碼塊的開始位置,monitorexit 指向結束位置。

sum.misc.Unsafe類中也包含類似方法,都是JNI調用,

public native void monitorEnter(Object o);
public native void monitorExit(Object o);

 

3.1 Java對象頭

對象頭:64
Mark Word:32 Klass Word:32

 

數組對象頭:96
Mark Word:32 Klass Word:32 Array Length:32

3.1.1 Mark Word

Mark Word是動態定義的數據結構,在對象不同鎖狀態時,存儲不同的數據,以便減少內存消耗

 

 鎖狀態 Mark Word
未鎖定 Normal hashcode:25
哈希碼
age:4
分代年齡
biased_lock:1
0
lock:2
01
偏向鎖 Biased thread:23
偏向線程
epoch:2
偏向時間戳
age:4
分代年齡
biased_lock:1
1
lock:2
01
輕量級鎖 Light ptr_to_lock_record:30
指向鎖記錄的指針
lock:2
00
重量級鎖 Heavy ptr_to_heavyweight_monitor:30
指向重量級鎖的指針
lock:2
10
GC標記 30
lock:2
11

3.1.2 Klass Word

3.2 鎖狀態

jdk1.6對synchronized進行了優化,引入了自旋鎖、偏向鎖、輕量級鎖、重量級鎖的概念。

自旋鎖:在未能獲取鎖的時候,該線程進入循環體空跑一段時間,如果跑完正好能夠獲取鎖就能避免切換線程等消耗。

偏向鎖、輕量級鎖和重量級鎖的轉化如下圖,下面一一介紹這3種鎖。

在代碼即將進入同步塊的時候,如果此同步對象沒有被鎖定(lock爲“01”狀態),且biased_lock=0

-XX:+UseBiased Locking

偏向鎖可用

1.lock=01,biased_lock=1,

2.CAS操作threadID到mark word(前23bit)

偏向鎖不可用,(jdk6以前)

用輕量級鎖

1.在棧上鎖記錄lock record拷貝mark word

2.CAS操作lock record指針到mark word

鎖膨脹到重量級鎖

 

 

3.2.1 偏向鎖 01

偏向鎖也是JDK 6中引入的一項鎖優化措施,目的是消除在無競爭情況下的同步原語,有些連CAS操作都不去做,進一步提高運行性能。偏向鎖可以通過-XX:+UseBiased Locking參數選擇開啓,從JDK6後默認開啓

線程競爭圖解 解釋

對比對象頭是否有當前線程的id

如果有時則直接重入,此時無需CAS

      (基於大多數情況不會鎖競爭,只是線程自己重入)

否則重新獲取鎖,無鎖狀態,且偏向鎖可用

當鎖對象第一次被線程1獲取的時候(CAS),

虛擬機將會把對象頭中的鎖標誌位=“01”、偏向模式=“1”,

表示進入偏向模式

一旦另外一個線程2先判斷線程id是否匹配,

不匹配就競爭這個鎖,偏向模式就馬上撤銷。

撤銷偏向(偏向模式設置爲“0”),

對象未鎖定:標誌位恢復到未鎖定(標誌位爲“01”)

對象已鎖定:輕量級鎖定(標誌位爲“00”)

CAS

原子操作Compare And Swap,CAS(V,A,B) ,邏輯如下,只是通過底層操作系統的原子指令操作

    boolean CAS(int V, int A, int B) {
        if (V == A) {
            V = B;
            return true;
        } else {
            return false;
        }
    }

當內存中的V值等於預期A值時,則將內存中的V值更新爲B值,並返回true,否則返回false。

偏向鎖CAS(markword, hash-0-01,t1-id-1-01)

僅當expect A是hash-0-01時纔會更新爲t1-id-1-01,併發CAS時只有第一個線程t1才能獲取成功,

t2去CAS時會因爲markwork與expect值hash-0-01不一致,操作失敗返回false

3.2.2 輕量級鎖 00

輕量級鎖同過CAS操作

線程競爭圖解 解釋

發生

不可偏向情況

或者偏向後發生競爭 升級到輕量級鎖

t1獲取輕量級鎖成功,

t2併發競爭失敗,此時發生自旋,等待輕量級鎖

 

虛擬機首先將在當前線程的棧幀中建立一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,如圖所示。

使用CAS(Mark Word,hash-0-01,ptr2LR-00) ,更新爲指向Lock Record的指針和鎖標誌00。如果這個更新動作成功了,即代表該線程擁有了這個對象的鎖,此對象處於輕量級鎖定狀態。

3.2.3 重量級鎖

依賴與底層操作系統的mutex lock互斥鎖實現,需要從用戶態切換到內核態,切換成本非常高。

一旦升級到重量級鎖,其它線程競爭鎖時,就直接進入阻塞隊列。

 

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