Java synchronized實現原理
一、簡介
synchronized是互斥同步的同步機制,互斥同步又稱堵塞同步。synchronized在多執行緒環境下,其中一條執行緒獲得鎖,其他執行緒需要堵塞等待持有鎖的執行緒釋放鎖。
synchronized是塊結構的同步語法,synchronized需要指定物件引數,對引數的引用就是reference。如果,synchronized沒有指定物件,Java編譯器通過synchronized修飾的方法檢查synchronized修飾的是物件方法還是類方法,分別為物件鎖和類鎖。
public class SynchronizedTest { private static finalVector<Integer> mVector = new Vector<>(); /** * synchronized 修飾靜態方法 * 類鎖 */ public synchronized static void printData1() { for (int i = 0; i < mVector.size(); i++) { System.out.println(mVector.get(i)); } } /** * synchronized 修飾物件方法 * 物件鎖*/ public synchronized void printData2() { for (int i = 0; i < mVector.size(); i++) { System.out.println(mVector.get(i)); } } /** * synchronized 保證塊中程式碼是互斥同步 * 物件鎖 * * synchronized 也可以修飾類,如:synchronized (Vector.class) {} */ public voidprintData() { synchronized (mVector) { for (int i = 0; i < mVector.size(); i++) { System.out.println(mVector.get(i)); } } } }
二、synchronized實現原理
將上面程式碼class檔案反彙編後,程式碼太長擷取部分:
public void printData(); Code: 0: getstatic #2 // Field mVector:Ljava/util/Vector; 3: dup 4: astore_1 5: monitorenter 6: iconst_0 7: istore_2 8: iload_2 9: getstatic #2 // Field mVector:Ljava/util/Vector; 12: invokevirtual #3 // Method java/util/Vector.size:()I 15: if_icmpge 37 18: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 21: getstatic #2 // Field mVector:Ljava/util/Vector; 24: iload_2 25: invokevirtual #5 // Method java/util/Vector.get:(I)Ljava/lang/Object; 28: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 31: iinc 2, 1 34: goto 8 37: aload_1 38: monitorexit 39: goto 47 42: astore_3 43: aload_1 44: monitorexit 45: aload_3 46: athrow 47: return
synchronized是通過monitorenter和monitorexit位元組碼指令實現的,monitorenter是獲取指定物件的鎖,monitorexit是釋放指定物件的鎖,分別對應Java虛擬機器位元組碼指令lock和unlock。Java虛擬機器位元組碼指令lock和unlock都是原子操作,所以,synchronized具有原子性。
monitorenter和monitorexit位元組碼指令需要指定reference型別的引數來說明鎖定和解鎖的物件。
synchronized需要指定物件引數,物件引數引用就是monitorenter和monitorexit位元組碼指令需要的reference。
執行monitorenter位元組碼指令時,執行位元組碼指令的執行緒需要嘗試獲取指定物件的鎖,如果,獲取指定的物件沒有鎖,又或者獲取指定物件的鎖已經被該執行緒鎖定,那麼,該物件鎖的計數器加1。如果,指定物件的鎖已經被其他執行緒持有,執行緒獲取鎖失敗,那麼,獲取該物件鎖失敗的執行緒需要堵塞等待,直到持有該物件鎖的執行緒釋放鎖為止。
執行monitorexit位元組碼指令時,指定物件鎖的計數器減1,直到計數器到0,持有該物件鎖的執行緒釋放該物件的鎖。
通過monitorenter和monitorexit位元組碼指令的機制,可以推測出synchronized的兩點結論:
-
- synchronized修飾的同步程式碼塊,允許執行緒重複獲取指定物件的鎖,只是物件的鎖的計數器加1。也就是說,獲取該物件鎖的執行緒允許重入獲取物件的鎖,而不用擔心死鎖問題。
- synchronized修飾的同步程式碼塊,一旦指定物件的鎖被執行緒持有,其他獲取該物件鎖的執行緒必須進入堵塞等待狀態,等待持有該物件鎖的執行緒釋放該物件鎖。也就是說,一旦指定物件的鎖被執行緒持有,其他獲取該物件鎖的執行緒不能強制已持有該物件鎖的執行緒釋放鎖,也不能將獲取該物件鎖失敗的被堵塞的執行緒中止或者超時退出等待。
三、synchronized效能
synchronized是重量級鎖,在主流的Java虛擬機器的執行緒模型是將Java虛擬機器的執行緒對映到系統核心執行緒之上的,執行緒的堵塞和喚醒需要作業系統完成,Java虛擬機器的執行緒是使用者執行緒,作業系統排程執行緒需要核心態和使用者態之間切換狀態,這部分很花費核心執行時間。比如:讓synchronized修飾getter()和setter()方法,核心態和使用者態的狀態切換比方法的執行時間都長。