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時,只有獲得鎖的執行緒才能再次獲得鎖。即可重入鎖。