簡單分析synchronized不會鎖洩漏的原因

kingsleylam發表於2019-06-05

最近看到一句話:內部鎖synchronized不會造成鎖洩漏(Lock Leak)。

鎖洩漏是指一個執行緒獲得某個鎖以後,由於程式的錯誤、缺陷致使該鎖一直沒法被釋放而導致其他執行緒一直無法獲得該鎖的現象。(摘自《Java多執行緒程式設計實戰指南(核心篇)》--黃文海)

很好奇JVM是怎麼保證的。

我想,Java程式碼,最終無非是編譯成位元組碼,變成一條條指令,或許可以從指令入手研究一下。

1. 一個小例子

我們先來看看下面的程式碼。

 1 public class SynchronizedTest {
 2 
 3     private static final Object LOCK = new Object();
 4 
 5     public static void main(String[] args) {
 6         synchronized (LOCK) {
 7             foo();
 8         }
 9     }
10 
11     public static void foo() {
12         //do something...
13     }
14 }

很簡單的一段程式碼,在臨界區呼叫了一個方法。

通過javap -c SynchronizedTest.class ,得到如下資訊,重點關注紅框部分:

 

第5和第10行分別是monitorenter 和 monitorexit,這表示進入和退出臨界區,臨界區中間第6行呼叫了foo()這個方法。當退出臨界區後,第11行直接goto第19行,即return,從當前方法返回,方法結束。

這是正常情況下的執行順序。

那麼如果在臨界區中發生了異常(本例中是foo()可能發生異常),執行順序又是怎麼樣的呢?

2. 異常表

在研究執行順序之前,先學習一下異常表。

異常表實際上是Java程式碼的一部分,編譯器使用異常表而不是簡單的跳轉命令來實現Java異常及finally處理機制。

上圖的 Exception table即為異常表。它列舉了哪些程式碼行的範圍內,可能出現某種異常時,對應的處理機制。

那麼異常表用在什麼時候呢?

答案是異常發生的時候,當一個異常發生時

1.JVM會在當前出現異常的方法中,查詢異常表,是否有合適的處理者來處理

2.如果當前方法異常表不為空,並且異常符合處理者的from和to節點,並且type也匹配,則JVM呼叫位於target的呼叫者來處理。

3.如果上一條未找到合理的處理者,則繼續查詢異常表中的剩餘條目

4.如果當前方法的異常表無法處理,則向上查詢(彈棧處理)剛剛呼叫該方法的呼叫處,並重覆上面的操作。

5.如果所有的棧幀被彈出,仍然沒有處理,則拋給當前的Thread,Thread則會終止。

6.如果當前Thread為最後一個非守護執行緒,且未處理異常,則會導致JVM終止執行。

以上就是JVM處理異常的一些機制。

】--以上摘自 詳解JVM如何處理異常

具體到本例中

1. 當臨界區發生異常,JVM查詢異常表,發現符合第一行記錄(異常發生點在6和11行(不包括第11行)之間,任意異常型別),於是跳到第14行,即第10行的monitorexit要麼不執行,要麼執行丟擲異常,總之無法成功執行。

2. 從14行一路執行下去,到16行,再次monitorexit,此時釋放鎖。18行丟擲上面獲取到的異常,19行從當前方法返回void, 結束方法的執行。

3. 如果第2步過程中又發生異常,根據異常表第二行記錄(異常點發生在14和17行(不包括第17行)之間,任意異常型別),會再次跳到14行,重新執行第2步,迴圈直到monitorexit指令成功執行。

這樣就保證了monitorexit一定能夠執行成功,鎖一定會被釋放。

相關文章