【多線程】synchronized背后的monitor鎖
monitor的作用
我們都知道synchronized的作用是用來保證修飾的代碼或者方法執行有且只有一個線程執行,也就是鎖。那么在執行被鎖住的方式時,synchronized就需要通過monitor來記錄和保證鎖的狀態。所以monitor這里的作用其實就是起到了控制synchronized什么時候獲取鎖,什么時候釋放鎖,以及記錄了鎖被重用的次數。
獲取和釋放monitor鎖的時機
被修飾的同步代碼塊
對于被synchronized修飾的代碼塊,在生成class字節碼文件中會出現monitorenter、monitorexit。如下面例子所示:
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 #1
4: getstatic
7: ldc
9: invokevirtual
12: aload_1
13: monitorexit #2
14: goto
17: astore_2
18: aload_1
19: monitorexit #3
20: aload_2
21: athrow
22: return
執行monitorenter的線程會嘗試獲取monitor的所有權,會發生以下三種情況之一:
- 如果該monitor的計數為0,這線程獲得該monitor鎖并設置為1
- 如果當前線程有了這個monitor鎖,則該線程的monitor的計數累加1
- 如果其他線程嘗試獲取monitor鎖,發現monitor的計數不為0,這表示當前線程被其他線程占用,則阻塞,直到這個monitor鎖的計數變為0,然后再重新嘗試獲取。
執行monitorexit的線程就會將montior的計數減1,直到減到0為止,這時候就表示可以釋放當前montior的鎖了,其他的線程就可以嘗試來獲取當前代碼的鎖了。
看到這里,可能會有疑問,為什么生成的字節碼文件中,一個monitorenter為什么對存在兩個monitorexit,這里其實是考慮到代碼發生了異常的情況,當我們在正常執行完任務之后,會執行#2的monitorexit去釋放鎖,但是出現異常了就會去執行#3的monitorexit的鎖。這樣就避免了死鎖的發生,保證在任何情況下都能正常釋放鎖。
被修飾的同步方法
同步代碼塊是使用monitorenter和monitorexit來實現的,對于方法則不是依靠它兩來實現的二十通過一個ACC_SYNCHRONIZED的flag修飾符,源代碼如下:
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
當某個線程需要訪問這個方法的時候,會先檢查這個方法是否有ACC_SYNCHRONIZED這個標簽,如果有就需要先獲取monitor鎖,其他的方面和同步代碼塊的邏輯是一樣的。

浙公網安備 33010602011771號