synchronized背后的monitor鎖實現(xiàn)詳解
獲取和釋放 monitor 鎖的時機
本文我們研究下 synchronized 背后的 monitor 鎖。
我們都知道,最簡單的同步方式就是利用 synchronized 關(guān)鍵字來修飾代碼塊或者修飾一個方法,那么這部分被保護的代碼,在同一時刻就最多只有一個線程可以運行,而 synchronized 的背后正是利用 monitor 鎖實現(xiàn)的。所以首先我們來看下獲取和釋放 monitor 鎖的時機,每個 Java 對象都可以用作一個實現(xiàn)同步的鎖,這個鎖也被稱為內(nèi)置鎖或 monitor 鎖,獲得 monitor 鎖的唯一途徑就是進入由這個鎖保護的同步代碼塊或同步方法,線程在進入被 synchronized 保護的代碼塊之前,會自動獲取鎖,并且無論是正常路徑退出,還是通過拋出異常退出,在退出的時候都會自動釋放鎖。
我們首先來看一個 synchronized 修飾方法的代碼的例子:
public synchronized void method() {
method body
}
我們看到 method() 方法是被 synchronized 修飾的,為了方便理解其背后的原理,我們把上面這段代碼改寫為下面這種等價形式的偽代碼。
public void method() {
this.intrinsicLock.lock();
try{
method body
}
finally {
this.intrinsicLock.unlock();
}
}
在這種寫法中,進入 method 方法后,立刻添加內(nèi)置鎖,并且用 try 代碼塊把方法保護起來,最后用 finally 釋放這把鎖,這里的 intrinsicLock 就是 monitor 鎖。經(jīng)過這樣的偽代碼展開之后,相信你對 synchronized 的理解就更加清晰了。
用 javap 命令查看反匯編的結(jié)果
JVM 實現(xiàn) synchronized 方法和 synchronized 代碼塊的細(xì)節(jié)是不一樣的,下面我們就分別來看一下兩者的實現(xiàn)。
同步代碼塊
首先我們來看下同步代碼塊的實現(xiàn),如代碼所示。
public class SynTest {
public void synBlock() {
synchronized (this) {
System.out.println("lagou");
}
}
}
在 SynTest 類中的 synBlock 方法,包含一個同步代碼塊,synchronized 代碼塊中有一行代碼打印了 lagou 字符串,下面我們來通過命令看下 synchronized 關(guān)鍵字到底做了什么事情:首先用 cd 命令切換到 SynTest.java 類所在的路徑,然后執(zhí)行 javac SynTest.java,于是就會產(chǎn)生一個名為 SynTest.class 的字節(jié)碼文件,然后我們執(zhí)行 javap -verbose SynTest.class,就可以看到對應(yīng)的反匯編內(nèi)容。
關(guān)鍵信息如下:
public void synBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
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 lagou
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
從里面可以看出,synchronized 代碼塊實際上多了 monitorenter 和 monitorexit 指令,標(biāo)紅的第3、13、19行指令分別對應(yīng)的是 monitorenter 和 monitorexit。這里有一個 monitorenter,卻有兩個 monitorexit 指令的原因是,JVM 要保證每個 monitorenter 必須有與之對應(yīng)的 monitorexit,monitorenter 指令被插入到同步代碼塊的開始位置,而 monitorexit 需要插入到方法正常結(jié)束處和異常處兩個地方,這樣就可以保證拋異常的情況下也能釋放鎖
可以把執(zhí)行 monitorenter 理解為加鎖,執(zhí)行 monitorexit 理解為釋放鎖,每個對象維護著一個記錄著被鎖次數(shù)的計數(shù)器。未被鎖定的對象的該計數(shù)器為 0,我們來具體看一下 monitorenter 和 monitorexit 的含義:
- monitorenter
執(zhí)行 monitorenter 的線程嘗試獲得 monitor 的所有權(quán),會發(fā)生以下這三種情況之一:
a. 如果該 monitor 的計數(shù)為 0,則線程獲得該 monitor 并將其計數(shù)設(shè)置為 1。然后,該線程就是這個 monitor 的所有者。
b. 如果線程已經(jīng)擁有了這個 monitor ,則它將重新進入,并且累加計數(shù)。
c. 如果其他線程已經(jīng)擁有了這個 monitor,那個這個線程就會被阻塞,直到這個 monitor 的計數(shù)變成為 0,代表這個 monitor 已經(jīng)被釋放了,于是當(dāng)前這個線程就會再次嘗試獲取這個 monitor。
- monitorexit monitorexit 的作用是將 monitor 的計數(shù)器減 1,直到減為 0 為止。代表這個 monitor 已經(jīng)被釋放了,已經(jīng)沒有任何線程擁有它了,也就代表著解鎖,所以,其他正在等待這個 monitor 的線程,此時便可以再次嘗試獲取這個 monitor 的所有權(quán)。
同步方法
從上面可以看出,同步代碼塊是使用 monitorenter 和 monitorexit 指令實現(xiàn)的。而對于 synchronized 方法,并不是依靠 monitorenter 和 monitorexit 指令實現(xiàn)的,被 javap 反匯編后可以看到,synchronized 方法和普通方法大部分是一樣的,不同在于,這個方法會有一個叫作 ACC_SYNCHRONIZED 的 flag 修飾符,來表明它是同步方法。
同步方法的代碼如下所示。
public synchronized void synMethod() {
}
對應(yīng)的反匯編指令如下所示。
public synchronized void synMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 16: 0
可以看出,被 synchronized 修飾的方法會有一個 ACC_SYNCHRONIZED 標(biāo)志。當(dāng)某個線程要訪問某個方法的時候,會首先檢查方法是否有 ACC_SYNCHRONIZED 標(biāo)志,如果有則需要先獲得 monitor 鎖,然后才能開始執(zhí)行方法,方法執(zhí)行之后再釋放 monitor 鎖。其他方面, synchronized 方法和剛才的 synchronized 代碼塊是很類似的,例如這時如果其他線程來請求執(zhí)行方法,也會因為無法獲得 monitor 鎖而被阻塞。
好了,本文的內(nèi)容就全部講完了,我們講解了獲取和釋放 monitor 的時機,以及被 synchronized 修飾的等價代碼,然后我們還利用 javac 和 javap 命令查看了 synchronized 代碼塊以及 synchronized 方法所對應(yīng)的的反匯編指令,其中同步代碼塊是利用 monitorenter 和 monitorexit 指令實現(xiàn)的,而同步方法則是利用 flags 實現(xiàn)的。
更多關(guān)于synchronized monitor鎖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java常見的數(shù)據(jù)結(jié)構(gòu)之棧和隊列詳解
這篇文章主要介紹了Java常見的數(shù)據(jù)結(jié)構(gòu)之棧和隊列詳解,棧(Stack) 是一種基本的數(shù)據(jù)結(jié)構(gòu),具有后進先出(LIFO)的特性,類似于現(xiàn)實生活中的一疊盤子,棧用于存儲一組元素,但只允許在棧頂進行插入(入棧)和刪除(出棧)操作,需要的朋友可以參考下2023-10-10
Java利用opencv實現(xiàn)用字符展示視頻或圖片的方法
這篇文章主要介紹了Java利用opencv實現(xiàn)用字符展示視頻或圖片的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
Java?設(shè)計模式以虹貓藍(lán)兔的故事講解原型模式
原型模式是用于創(chuàng)建重復(fù)的對象,同時又能保證性能。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式,今天通過本文給大家介紹下Java 原型設(shè)計模式,感興趣的朋友一起看看吧2022-04-04

