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編譯器爲咱們生成的字節碼。在對於doSth
和doSth1
的處理上稍有不一樣。也就是說。JVM對於同步方法和同步代碼塊的處理方式不一樣。oracle
對於同步方法,JVM採用ACC_SYNCHRONIZED
標記符來實現同步。 對於同步代碼塊。JVM採用monitorenter
、monitorexit
兩個指令來實現同步。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
同步代碼塊使用monitorenter
和monitorexit
兩個指令實現。 The Java® Virtual Machine Specification 中有關於這兩個指令的介紹:orm
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.
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
時,須要先得到鎖才能執行該方法。同步代碼塊經過
monitorenter
和monitorexit
執行來進行加鎖。當線程執行到monitorenter
的時候要先得到所鎖,才能執行後面的方法。當線程執行到monitorexit
的時候則要釋放鎖。每一個對象自身維護這一個被加鎖次數的計數器,當計數器數字爲0時表示能夠被任意線程得到鎖。當計數器不爲0時,只有得到鎖的線程才能再次得到鎖。便可重入鎖。
至此,咱們大體瞭解了Synchronized的原理。可是還有幾個問題並無介紹清楚,好比,Monitor究竟是什麼?對象的鎖的狀態保存在哪裏? 別急,後面會再介紹。