深刻理解多線程(一)——Synchronized的實現原理

synchronized,是Java中用於解決併發狀況下數據同步訪問的一個很重要的關鍵字。當咱們想要保證一個共享資源在同一時間只會被一個線程訪問到時,咱們能夠在代碼中使用synchronized關鍵字對類或者對象加鎖。那麼,本文來介紹一下synchronized關鍵字的實現原理是什麼。在閱讀本文之間,建議先看下Java虛擬機是如何執行線程同步的html

反編譯

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

/**
 * @author Hollis 17/11/9.
 */
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編譯器爲咱們生成的字節碼。在對於doSthdoSth1的處理上稍有不一樣。也就是說。JVM對於同步方法和同步代碼塊的處理方式不一樣。oracle

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

關於這部份內容,在JVM規範中也能夠找到相關的描述。ui

同步方法

The Java® Virtual Machine Specification中有關於方法級同步的介紹:spa

Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool's method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.線程

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

同步代碼塊

同步代碼塊使用monitorentermonitorexit兩個指令實現。 The Java® Virtual Machine Specification 中有關於這兩個指令的介紹:orm

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.

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.

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

總結

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

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

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

至此,咱們大體瞭解了Synchronized的原理。可是還有幾個問題並無介紹清楚,好比,Monitor究竟是什麼?對象的鎖的狀態保存在哪裏? 別急,後面會再介紹。

相關文章
相關標籤/搜索