摘要:synchronized鎖修飾方法和代碼塊時底層實作上是一樣的,但是在修飾方法時,不需要JVM編譯出的位元組碼完成加鎖操作,而synchronized在修飾代碼塊時,是通過編譯出來的位元組碼生成的monitorenter和monitorexit指令來實作的,
本文分享自華為云社區《Synchronized底層核心原理》,作者: 小威要向諸佬學習呀 ,
synchronized鎖用于同步實體方法,同步靜態方法和同步代碼塊,自從Java1.6開始,就對synchronized鎖進行了很多方面的優化,對其引入了偏向鎖,輕量級鎖,適應性自旋鎖,鎖粗化,鎖消除等各種技術方面的優化,
synchronized鎖是基于monitor鎖實作的,因此在講解synchronized鎖之前,有必要了解一下monitor鎖,
monitor鎖的原理
monitor,在中文中有監視器的意思,當創建物件時,每一個創建出來的物件都會關聯一個monitor物件,對于一個java物件,當拿到這個monitor物件時,這個monitor物件就會處于鎖定的狀態,其他物件不會再獲取,synchronized鎖的本質就是基于進入和退出monitor物件實作的同步方法和同步代碼塊,
這里首先解釋一下wait,notify,notifyAll等方法的各個作用:
wait方法會讓進入object監視器的執行緒進入到WaitSet集合中等待;
notify方法會使在object上正在WaitSet集合上等待的執行緒中挑一個喚醒執行緒;
notifyAll方法會讓正在WaitSet集合中等待的執行緒全部喚醒,
而對于monitor,它是基于ObjectMonitor實作的,ObjectMonitor的主要資料結構包括:
owner:owner原本的值為null,它用來指向獲取到ObjectMonitor物件的執行緒,當一個執行緒獲取到ObjectMonitor物件時,這個ObjectMonitor物件就會存盤在當前物件的物件頭中的Mark
Word中,
WaitSet,這個是ObjectMonitor中的一個集合,同時WaitSet與wait()方法有關,當Owner執行緒發現條件不滿足時,會呼叫wait方法,使執行緒進入WaitSet集合中變為WAITING狀態,
EntryList,也是ObjectMonitor中的一個集合,同時EntryList與notify(),notifyAll()方法有關,WAITING狀態下的執行緒會在Owner執行緒呼叫notify()或notifyAll()等方法時喚醒,但是喚醒之后并不代表著執行緒會立即拿到鎖資源,而是需要進入EntryList集合中進行競爭,
模擬多執行緒情況下,同時訪問一個被synchronized鎖修飾方法時,在JVM底層中的流程如下·:
- 執行緒進入EntryList集合時,如果某個執行緒獲取到monitor物件時,這個執行緒會進入owner中,同時會把monitor物件中的owner變數復制為當前的執行緒(拿到monitor物件的這個),并且會把monitor物件中的count變數值+1,
- 如果執行緒呼叫wait方法,當前的執行緒就會釋放拿到的monitor物件,并且會把monitor物件中的owner變數值設為null,并且count的值-1,最后,當前執行緒會進入到WaitSet集合中等待,等候再次被喚醒,
- 如果是獲得monitor物件的執行緒執行任務完成后,也會進行上面的一系列操作,但不會到WaitSet集合中等待了,因為任務已經執行完了,
synchronized修飾方法
前面說到synchronized鎖是基于monitor鎖實作的,當synchronized鎖修飾方法時,被此鎖修飾的方法會比普通方法的常量池中多一個ACC_SYNCHRONIZED識別符號,當執行緒呼叫了被synchronized鎖修飾的方法時,會檢查方法中是否設定了此識別符號,
如果設定了ACC_SYNCHRONIZED識別符號,那么當前的執行緒會首先獲取monitor鎖物件,然后執行同步代碼中的方法,完成后會釋放monitor物件,當然,在多執行緒情況下,只有一個執行緒能夠獲取此monitor物件,并且在該執行緒釋放monitor物件之前,其他執行緒無法獲取此monitor物件,因此在同一時刻,只能有一個執行緒拿到相同物件的synchronized鎖資源,
而當synchronized鎖修飾代碼塊時,與synchronized修飾方法略有不同,接下來詳細講解synchronized修飾代碼塊的情況,
synchronized修飾代碼塊
當synchronized鎖修飾代碼塊時,synchronized關鍵字會被編譯成monitorenter和monitorexit兩條指令,其中,monitorenter會放在代碼塊的前面,而monitorexit會放在代碼塊的后面,
對于monitorenter指令:
每個物件都擁有一個monitor,當monitor被占用時,就會處于鎖定狀態,執行緒執行monitorenter指令時會獲取monitor的所有權,
當monitor計數為0時,說明該monitor還未被鎖定,此時執行緒會進入monitor并將monitor的計數器設為1,并且該執行緒就是monitor的所有者,
如果此執行緒已經獲取到了monitor鎖,再重新進入monitor鎖的話,那么會將計時器count的值加1,
如果有執行緒已經占用了monitor鎖,此時有其他的執行緒來獲取鎖,那么此執行緒將進入阻塞狀態,待monitor的計時器count變為0,這個執行緒才會獲取到monitor鎖,
對于monitorexit指令:
首先,只有拿到了monitor鎖物件的執行緒才會執行monitorexit指令,
其次就是,在執行monitorexit指令時,計時器count的值會減1,當count的值減到0時,當前的執行緒才會退出monitor,此時的執行緒不再是monitor的所有者,當然執行后,其他執行緒可以獲取當前monitor鎖的所有權,
通過對簡單代碼進行反編譯來舉例:
public class SynchronizedTest { public void synchronize(){ synchronized (this){ System.out.println("hello world"); } } }
執行 javap -c SynchronizedTest.class指令得到以下位元組碼:
public class Synchronized.SynchronizedTest { public Synchronized.SynchronizedTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void synchronize(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String hello world 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any }
由上述編碼可以看出,在synchronized修飾的代碼塊中,存在有monitorenter指令和monitorexit指令,
synchronized鎖總結
因此,由以上可以得出,synchronized鎖修飾方法和代碼塊時底層實作上是一樣的,但是在修飾方法時,不需要JVM編譯出的位元組碼完成加鎖操作,而synchronized在修飾代碼塊時,是通過編譯出來的位元組碼生成的monitorenter和monitorexit指令來實作的,
點擊關注,第一時間了解華為云新鮮技術~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/529828.html
標籤:Java
上一篇:MybatisPlus Lambda運算式 聚合查詢 分組查詢 COUNT SUM AVG MIN MAX GroupBy
下一篇:死鎖的3種死法
