【併發編程】 --- 從字節碼指令的角度去理解synchronized關鍵字的原理

源碼地址:https://github.com/nieandsun/concurrent-study.git


不知道大家有沒有這樣一種感覺,對於synchronized關鍵字我(1)會使用,(2)懂原理,但仍然還是想找到官方的證明,告訴自己這一切都是正確的,對的。 — 或許不止synchronized關鍵字,好多問題貌似都這樣。。。


1 synchronized & 字節碼指令

首先應該知道,我們寫的代碼最終都會被轉化爲一條條的字節碼指令,運行在JVM上。

比如如下代碼:

package com.nrsc.ch1.base.jmm.syn_study.deep;

public class SynDeepStudy {

    public void test1() {
        synchronized (this) {
            System.out.println("test1");
        }
    }

    public synchronized void test2() {
        System.out.println("test2");
    }

    public static synchronized void test3() {
        System.out.println("test3");
    }
}

我們可以先找到其編譯後的.class文件 — SynDeepStudy.class,然後通過javap命令反彙編得到其字節碼指令,比如我用下面的命令將其反彙編結果輸出到一個txt文件裏 :

javap -p -v -c SynDeepStudy.class > syn.txt

下圖是方法test1()對應的字節碼指令:
在這裏插入圖片描述
從上圖我們至少可以看到兩點:

  • (1)進入synchronized代碼塊之前會插入一個monitorenter指令,出去時會插入一條monitorexit
  • (2)如果synchronized代碼塊裏發生異常時,也會走monitorexit指令 —》 即釋放鎖。

接下來從JDK官網看一看對這個monitorenter和monitorexit指令的描述。

2 字節碼指令 — monitorenter

官網地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter

官網對monitorenter指令的描述如下:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

  • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
  • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
  • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

翻譯如下:
每一個對象都會有一個monitor對象與之進行關聯。 這個monitor對象只有在擁有所有者的情況下才會被鎖定。 執行monitorenter指令的線程嘗試獲取鎖對象對應的monitor所有權的過程如下:

  • (1) 若monior的進入數爲0,線程可以進入monitor,並將monitor的進入數置爲1。當前線程成爲 monitor的owner(所有者)。
  • (2) 若線程已擁有monitor的所有權,允許它重入monitor,則進入monitor的進入數加1。
  • (3) 若其他線程已經佔有monitor的所有權,那麼當前嘗試獲取monitor所有權的線程會被阻塞,直到monitor的進入數變爲0,才能重新嘗試獲取monitor的所有權。

聯繫上文《【併發編程】 — synchronized/ReentrantLock兩大特性(可重入性和不可中斷性)介紹》提到的鎖的重入,這裏再重新敘述一遍:

synchronized的鎖對象會關聯一個monitor,這個monitor不是我們主動創建的,是JVM的線程執行到這個
同步代碼塊,發現鎖對象沒有monitor就會創建一個monitor,monitor內部有兩個重要的成員變量owner:擁有這把鎖的線程;recursions:會記錄線程擁有鎖的次數(重入的次數)。當一個線程擁有monitor後其他線程只能等待。


3 字節碼指令 — monitorexit

官網地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorexit

官網對monitorexit指令的描述如下:

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

翻譯如下:

  • (1) 能執行monitorexit指令的線程一定是擁有當前鎖對象的monitor所有權的線程。
  • (2)執行monitorexit時會將monitor的進入數減1。當monitor的進入數減爲0時,當前線程退出
    monitor,不再擁有monitor的所有權,此時其他被這個monitor阻塞的線程可以嘗試去獲取這個
    monitor的所有權。

結合1中的圖可以看到:
monitorexit插入在同步代碼塊結束處和異常處。JVM會保證每個monitorenter必須有對應的monitorexit。


4 同步方法對應的字節碼指令

接下來看一下1中代碼裏test2()和test3()方法對應的字節碼指令:
在這裏插入圖片描述
從圖中可以看到,無論是靜態同步代碼塊,還是非靜態同步代碼塊對應的字節碼指令:

  • (1)都沒有出現monitorentermonitorexit指令;
  • (2)在方法上都多了一個修飾符ACC_SYNCHRONIZED

這是怎麼回事呢?查看官網:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10可知:

執行同步代碼塊會隱式地調用monitorenter和monitorexit指令 — 在執行同步方法前會調用monitorenter,在執行完同步方法後會調用monitorexit。


小結

通過本文前面4小結,可以得到如下結論:

使用synchronized關鍵字修飾的同步代碼塊或同步方法,在轉爲JVM運行的字節碼指令時,會在前後插入monitorentor和monitorexit兩個指令。每個作爲鎖的對象都會關聯一個monitor對象(其實該monitor對象纔是真正的鎖對象),該對象內部有兩個重要的成員變量:

  • owner會保存獲得鎖的線程
  • recursions會保存線程獲得鎖的次數(或者說可重入的次數)

當執行到monitorexit時,recursions會-1,當recursions減到0時這個線程就會釋放鎖;
當然如果同步代碼塊裏出現了異常也會釋放鎖。


需要思考的問題

不知道你有沒有這樣一種感覺:

雖然從字節碼指令的角度知道了synchronized關鍵字的原理,但是仍然會對它有很多疑惑,比如說:

  • (1)這個monitor對象到底是什麼???
  • (2)爲什麼很多資料上都說monitor是重量級鎖???
  • (3)相信大家都知道jdk1.6對synchronized關鍵字進行了優化
    • 優化中涉及到了鎖對象對象頭中的mark word,那這和monitor有啥關係???
    • monitorentor、monitorexit這兩個字節碼指令和mark word 以及monitor到底又是啥關係???

如果理不清這些問題,肯定你也會覺得synchronized關鍵字的瞭解還差點什麼。。。


end

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