java併發編程(六)synchronized原理 及 輕量級鎖 一、Monitor 二、synchronized原理分析

上一篇文章帶大家簡單瞭解了對象頭,及mark word的內容,那麼本文將來學習,mark word到底有什麼作用。其實就是synchronized的原理。

先將64位虛擬機的java對象Mark Word放在這,方便後面查看:

|----------------------------------------------------------------------------------------------|
|                                   Mark Word(64bits)                     |      State         |
|----------------------------------------------------------------------------------------------|
|    unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:0| lock:01  |      Nomal         |
|----------------------------------------------------------------------------------------------|
|    thread:54|      epoch:2       |unused:1|age:4|biase_lock:1| lock:01  |      Biased        |
|----------------------------------------------------------------------------------------------|
|                        ptr_to_lock_record:62                 | lock:00  | Lightweight Locked |
|----------------------------------------------------------------------------------------------|
|                       ptr_to_heavyweight_monitor:62          | lock:10  | Heavyweight Locked |
|----------------------------------------------------------------------------------------------|
|                                                              | lock:11  |    Marked for GC   |
|----------------------------------------------------------------------------------------------|

一、Monitor

在介紹Mark Word的時候,提到過Monitor,可以稱爲監視器或管程,下面我們看下它到底是個什麼東西。

每個 Java 對象都可以關聯一個 Monitor 對象,如果使用 synchronized 給對象上鎖(重量級)之後,該對象頭的Mark Word 中就被設置指向 Monitor 對象的指針。不加 synchronized 的對象不會關聯Monitor。

首先通過下圖展示管程的組成,以及和java對象的關係:

  • 有一個java對象,上了一把synchronized(重量級鎖),當有線程獲取到鎖時,其對象頭中的Mark Word變成了指向Monitor的指針。(原本mark word當中的內容會存儲到Monitor當中,釋放時會取出這些內容再次放到mark word。)

  • thread3 來競爭這把鎖,此時只有它自己,那麼thread3將會被設置爲Monitor的Owner,有且只能有一個Owner。

  • 如果thread3持有鎖的過程中,如果thread4和thread5也來競爭鎖,就會添加到EntryList當中,此時線程將被阻塞(BLOCKED)。

  • 當thread執行完同步代碼塊當中的內容,會喚醒EntryList當中的線程來競爭鎖,此競爭是非公平的。

  • 另外,在WaitSet當中的thread1和thread2,其狀態是WAITING,表示他們之前獲得過鎖,但是條件不滿足,此處不講解,後面在分析wait,notify時講解。

二、synchronized原理分析

2.1 synchronized字節碼分析

在上面瞭解Monitor後,進入java當中的重點,synchronized的學習。

有如下代碼:

    static final Object object = new Object();
    static int i = 0;

    public static void main(String[] args) {
        synchronized (object) {
            i++;
        }
    }

其字節碼如下所示:

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // 獲取鎖對象object
       3: dup                               // 拷貝一份
       4: astore_1                          // 將拷貝的變量存儲到 slot 1中
       5: monitorenter                      // 將Monitor指針設置到lock對象的Mark Word
       6: getstatic     #3                  // 獲取靜態變量i
       9: iconst_1                          // 準備常數 1
      10: iadd                              // 執行++ 操作
      11: putstatic     #3                  // 將i當前值賦值給靜態變量
      14: aload_1                           // 獲取對象鎖
      15: monitorexit                       // 重置對象的Mark Word,喚醒EntryList當中的線程
      16: goto          24                  // 跳轉到24 行
      19: astore_2                          // 將異常存儲到 slot 2 中
      20: aload_1                           // 獲取鎖對象
      21: monitorexit                       // 重置對象的Mark Word,喚醒EntryList當中的線程
      22: aload_2                           // 獲取slot 2中的異常
      23: athrow                            // 拋出異常
      24: return                            // 方法返回
    Exception table:
       from    to  target type
           6    16    19   any              // 6 ~ 16 行爲正常代碼運行邏輯,如果在這之間發生了異常,則代碼跳轉值第19行
          19    22    19   any              // 19 ~ 22 是異常時,代碼執行的邏輯

關於上面字節碼的意思都在註釋中。

注意:在方法層面上的鎖,在字節碼當中不會有鎖體現,如下:

    static final Object object = new Object();
    static int i = 0;

    public static synchronized void add() {
        i++;
    }

    public static void main(String[] args) {
        add();
    }

字節碼如下:

public static synchronized void add();
    Code:
       0: getstatic     #2                  // Field i:I
       3: iconst_1
       4: iadd
       5: putstatic     #2                  // Field i:I
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #3                  // Method add:()V
       3: return

2.2 輕量級鎖

如開篇展示的對象Mark Word,共有5種鎖的狀態, 我們本小節講解其中之一,輕量級鎖相關的內容。

輕量級鎖是指在滿足一定的條件內,使用CAS(自旋)來嘗試獲取對象鎖的一種機制,如果超過以下條件,則會膨脹爲重量級鎖:

1)在jdk1.6前,默認10次,可通過-XX:PreBlockSpin來修改,或者自旋線程數超過CPU核數的一半。

2)jdk1.6之後,引入了自適應自旋鎖,次數並非一成不變。根據獲取鎖的成功率來決定是否能有更長的等待時間。

輕量級鎖仍然是使用synchronized,用戶其實是無感知的。

2.2.1 輕量級鎖的上鎖和釋放鎖

假設當前有一把對象鎖lock,兩個方法使用同一把鎖,且add當中會調用sub()方法,如下所示:

    static final Object lock = new Object();

    public void add(){
        synchronized (lock){
            sub();
        }
    }

    public void sub(){
        synchronized (lock){
            
        }
    }
  • 當線程嘗試獲取這把鎖的時候,會創建鎖記錄(Lock Record),每個線程的棧幀(線程執行到的方法,都會生成對應的棧幀),都會包含一個鎖記錄的結構,可以用來存儲對象的Mark Word,如下所示:
  • 讓鎖記錄中 Object Reference 指向鎖對象,並嘗試用 CAS 替換 Object 的 Mark Word,將 Mark Word 的值存入鎖記錄(如圖上的1),並將鎖記錄的地址存入Object的Mark Word(如圖上的2),如下所示:
  • 如果CAS成功,對象的Mark Word將會存儲Lock Record 地址 和 鎖狀態 00,如下圖所示:
  • 如果CAS失敗,此時會有兩種情況:
    • 如果是其他線程已經持有了該輕量級鎖,則表示發生競爭,此時進入鎖膨脹。
    • 如果是自己執行了 synchronized 鎖重入(就像我們示例代碼中的add()方法),那麼再添加一條 Lock Record 作爲重入的計數,只不過新添加的Lock Record中沒有Object的Mark word內容,爲null。如下所示:
  • 當退出 synchronized 代碼塊(解鎖時)如果有取值爲 null 的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數減一,將null的Lock Record刪除。

  • 當退出 synchronized 代碼塊(解鎖時)鎖記錄的值不爲 null,這時使用 CAS 將 Mark Word 的值恢復給對象頭。

    • 成功,則解鎖成功。
    • 失敗,說明輕量級鎖進行了鎖膨脹或已經升級爲重量級鎖,進入重量級鎖解鎖流程。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章