synchronized的實現原理

前言

synchronized,是Java中用於解決併發情況下數據同步訪問的一個很重要的關鍵字。當我們想要保證一個共享資源在同一時間只會被一個線程訪問到時,我們可以在代碼中使用synchronized關鍵字對類或者對象加鎖。

使用形式

衆所周知,在Java中,synchronized有兩種使用形式,同步方法和同步代碼塊。代碼如下:

public class SynchronizedTest {

    public synchronized void doSth(){
        System.out.println("Hello World");
    }

    public void doSth1(){
        synchronized (SynchronizedTest.class){
            System.out.println("Hello World");
        }
    }
}

我們先來使用Javap來反編譯以上代碼,結果如下(部分無用信息過濾掉了):

public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

  public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/hollis/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String Hello World
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return

反編譯後,我們可以看到Java編譯器爲我們生成的字節碼。在對於doSth和doSth1的處理上稍有不同。也就是說。JVM對於同步方法和同步代碼塊的處理方式不同。

對於同步方法,JVM採用ACC_SYNCHRONIZED標記符來實現同步。 對於同步代碼塊。JVM採用monitorenter、monitorexit兩個指令來實現同步。

同步方法

方法級的同步是隱式的。同步方法的常量池中會有一個ACC_SYNCHRONIZED標誌。當某個線程要訪問某個方法的時候,會檢查是否有ACC_SYNCHRONIZED,如果有設置,則需要先獲得監視器鎖,然後開始執行方法,方法執行之後再釋放監視器鎖。這時如果其他線程來請求執行方法,會因爲無法獲得監視器鎖而被阻斷住。值得注意的是,如果在方法執行過程中,發生了異常,並且方法內部並沒有處理該異常,那麼在異常被拋到方法外面之前監視器鎖會被自動釋放。

同步代碼塊

可以把執行monitorenter指令理解爲加鎖,執行monitorexit理解爲釋放鎖。 每個對象維護着一個記錄着被鎖次數的計數器。未被鎖定的對象的該計數器爲0,當一個線程獲得鎖(執行monitorenter)後,該計數器自增變爲 1 ,當同一個線程再次獲得該對象的鎖的時候,計數器再次自增。當同一個線程釋放鎖(執行monitorexit指令)的時候,計數器再自減。當計數器爲0的時候。鎖將被釋放,其他線程便可以獲得鎖。

總結

同步方法通過ACC_SYNCHRONIZED關鍵字隱式的對方法進行加鎖。當線程要執行的方法被標註上ACC_SYNCHRONIZED時,需要先獲得鎖才能執行該方法。

同步代碼塊通過monitorenter和monitorexit執行來進行加鎖。當線程執行到monitorenter的時候要先獲得所鎖,才能執行後面的方法。當線程執行到monitorexit的時候則要釋放鎖。

每個對象自身維護這一個被加鎖次數的計數器,當計數器數字爲0時表示可以被任意線程獲得鎖。當計數器不爲0時,只有獲得鎖的線程才能再次獲得鎖。即可重入鎖。

原文鏈接:http://www.hollischuang.com/archives/1883

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