synchronized背后的monitor鎖實(shí)現(xiàn)詳解
獲取和釋放 monitor 鎖的時(shí)機(jī)
本文我們研究下 synchronized 背后的 monitor 鎖。
我們都知道,最簡(jiǎn)單的同步方式就是利用 synchronized 關(guān)鍵字來修飾代碼塊或者修飾一個(gè)方法,那么這部分被保護(hù)的代碼,在同一時(shí)刻就最多只有一個(gè)線程可以運(yùn)行,而 synchronized 的背后正是利用 monitor 鎖實(shí)現(xiàn)的。所以首先我們來看下獲取和釋放 monitor 鎖的時(shí)機(jī),每個(gè) Java 對(duì)象都可以用作一個(gè)實(shí)現(xiàn)同步的鎖,這個(gè)鎖也被稱為內(nèi)置鎖或 monitor 鎖,獲得 monitor 鎖的唯一途徑就是進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或同步方法,線程在進(jìn)入被 synchronized 保護(hù)的代碼塊之前,會(huì)自動(dòng)獲取鎖,并且無論是正常路徑退出,還是通過拋出異常退出,在退出的時(shí)候都會(huì)自動(dòng)釋放鎖。
我們首先來看一個(gè) synchronized 修飾方法的代碼的例子:
public synchronized void method() { method body }
我們看到 method() 方法是被 synchronized 修飾的,為了方便理解其背后的原理,我們把上面這段代碼改寫為下面這種等價(jià)形式的偽代碼。
public void method() { this.intrinsicLock.lock(); try{ method body } finally { this.intrinsicLock.unlock(); } }
在這種寫法中,進(jìn)入 method 方法后,立刻添加內(nèi)置鎖,并且用 try 代碼塊把方法保護(hù)起來,最后用 finally 釋放這把鎖,這里的 intrinsicLock 就是 monitor 鎖。經(jīng)過這樣的偽代碼展開之后,相信你對(duì) synchronized 的理解就更加清晰了。
用 javap 命令查看反匯編的結(jié)果
JVM 實(shí)現(xiàn) synchronized 方法和 synchronized 代碼塊的細(xì)節(jié)是不一樣的,下面我們就分別來看一下兩者的實(shí)現(xiàn)。
同步代碼塊
首先我們來看下同步代碼塊的實(shí)現(xiàn),如代碼所示。
public class SynTest { public void synBlock() { synchronized (this) { System.out.println("lagou"); } } }
在 SynTest 類中的 synBlock 方法,包含一個(gè)同步代碼塊,synchronized 代碼塊中有一行代碼打印了 lagou 字符串,下面我們來通過命令看下 synchronized 關(guān)鍵字到底做了什么事情:首先用 cd 命令切換到 SynTest.java 類所在的路徑,然后執(zhí)行 javac SynTest.java,于是就會(huì)產(chǎn)生一個(gè)名為 SynTest.class 的字節(jié)碼文件,然后我們執(zhí)行 javap -verbose SynTest.class,就可以看到對(duì)應(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 代碼塊實(shí)際上多了 monitorenter 和 monitorexit 指令,標(biāo)紅的第3、13、19行指令分別對(duì)應(yīng)的是 monitorenter 和 monitorexit。這里有一個(gè) monitorenter,卻有兩個(gè) monitorexit 指令的原因是,JVM 要保證每個(gè) monitorenter 必須有與之對(duì)應(yīng)的 monitorexit,monitorenter 指令被插入到同步代碼塊的開始位置,而 monitorexit 需要插入到方法正常結(jié)束處和異常處兩個(gè)地方,這樣就可以保證拋異常的情況下也能釋放鎖
可以把執(zhí)行 monitorenter 理解為加鎖,執(zhí)行 monitorexit 理解為釋放鎖,每個(gè)對(duì)象維護(hù)著一個(gè)記錄著被鎖次數(shù)的計(jì)數(shù)器。未被鎖定的對(duì)象的該計(jì)數(shù)器為 0,我們來具體看一下 monitorenter 和 monitorexit 的含義:
- monitorenter
執(zhí)行 monitorenter 的線程嘗試獲得 monitor 的所有權(quán),會(huì)發(fā)生以下這三種情況之一:
a. 如果該 monitor 的計(jì)數(shù)為 0,則線程獲得該 monitor 并將其計(jì)數(shù)設(shè)置為 1。然后,該線程就是這個(gè) monitor 的所有者。
b. 如果線程已經(jīng)擁有了這個(gè) monitor ,則它將重新進(jìn)入,并且累加計(jì)數(shù)。
c. 如果其他線程已經(jīng)擁有了這個(gè) monitor,那個(gè)這個(gè)線程就會(huì)被阻塞,直到這個(gè) monitor 的計(jì)數(shù)變成為 0,代表這個(gè) monitor 已經(jīng)被釋放了,于是當(dāng)前這個(gè)線程就會(huì)再次嘗試獲取這個(gè) monitor。
- monitorexit monitorexit 的作用是將 monitor 的計(jì)數(shù)器減 1,直到減為 0 為止。代表這個(gè) monitor 已經(jīng)被釋放了,已經(jīng)沒有任何線程擁有它了,也就代表著解鎖,所以,其他正在等待這個(gè) monitor 的線程,此時(shí)便可以再次嘗試獲取這個(gè) monitor 的所有權(quán)。
同步方法
從上面可以看出,同步代碼塊是使用 monitorenter 和 monitorexit 指令實(shí)現(xiàn)的。而對(duì)于 synchronized 方法,并不是依靠 monitorenter 和 monitorexit 指令實(shí)現(xiàn)的,被 javap 反匯編后可以看到,synchronized 方法和普通方法大部分是一樣的,不同在于,這個(gè)方法會(huì)有一個(gè)叫作 ACC_SYNCHRONIZED 的 flag 修飾符,來表明它是同步方法。
同步方法的代碼如下所示。
public synchronized void synMethod() { }
對(duì)應(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 修飾的方法會(huì)有一個(gè) ACC_SYNCHRONIZED 標(biāo)志。當(dāng)某個(gè)線程要訪問某個(gè)方法的時(shí)候,會(huì)首先檢查方法是否有 ACC_SYNCHRONIZED 標(biāo)志,如果有則需要先獲得 monitor 鎖,然后才能開始執(zhí)行方法,方法執(zhí)行之后再釋放 monitor 鎖。其他方面, synchronized 方法和剛才的 synchronized 代碼塊是很類似的,例如這時(shí)如果其他線程來請(qǐng)求執(zhí)行方法,也會(huì)因?yàn)闊o法獲得 monitor 鎖而被阻塞。
好了,本文的內(nèi)容就全部講完了,我們講解了獲取和釋放 monitor 的時(shí)機(jī),以及被 synchronized 修飾的等價(jià)代碼,然后我們還利用 javac 和 javap 命令查看了 synchronized 代碼塊以及 synchronized 方法所對(duì)應(yīng)的的反匯編指令,其中同步代碼塊是利用 monitorenter 和 monitorexit 指令實(shí)現(xiàn)的,而同步方法則是利用 flags 實(shí)現(xiàn)的。
更多關(guān)于synchronized monitor鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot?@DS注解實(shí)現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法
這篇文章主要給大家介紹了關(guān)于SpringBoot?@DS注解實(shí)現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法,所謂多數(shù)據(jù)源就是一個(gè)Java EE項(xiàng)目中采用了不同數(shù)據(jù)庫(kù)實(shí)例中的多個(gè)庫(kù),或者是同一個(gè)數(shù)據(jù)庫(kù)實(shí)例中的多個(gè)不同庫(kù),需要的朋友可以參考下2023-11-11Springboot詳解RocketMQ實(shí)現(xiàn)廣播消息流程
RocketMQ作為一款純java、分布式、隊(duì)列模型的開源消息中間件,支持事務(wù)消息、順序消息、批量消息、定時(shí)消息、消息回溯等,本篇我們了解如何實(shí)現(xiàn)廣播消息2022-06-06@Autowired與@Resource在實(shí)現(xiàn)對(duì)象注入時(shí)的區(qū)別
這篇文章主要介紹了@Autowired與@Resource在實(shí)現(xiàn)對(duì)象注入時(shí)的區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-04-04Java采用setAsciiStream方法檢索數(shù)據(jù)庫(kù)指定內(nèi)容實(shí)例解析
這篇文章主要介紹了Java采用setAsciiStream方法檢索數(shù)據(jù)庫(kù)指定內(nèi)容,是比較實(shí)用的功能,需要的朋友可以參考下2014-08-08詳解如何在項(xiàng)目中應(yīng)用SpringSecurity權(quán)限控制
本文主要介紹了如何在項(xiàng)目中應(yīng)用SpringSecurity權(quán)限控制,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06