前言
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時,只有獲得鎖的線程才能再次獲得鎖。即可重入鎖。