2 Java中的隱式鎖
在Java中,提供了關(guān)鍵字synchronized。這個(gè)關(guān)鍵字可以應(yīng)用在不同的地方,如下面的表格所示:
| 應(yīng)用位置 | 鎖存在于哪里 | 代碼示例 |
|---|---|---|
| 實(shí)例方法 | 當(dāng)前類(lèi)的實(shí)例對(duì)象 | public synchronized void method(){......} |
| 靜態(tài)方法 | 當(dāng)前類(lèi)對(duì)象 | public static synchronized void method(){......} |
| 代碼塊 | 指定的實(shí)例對(duì)象 | synchronized(object){......} //這里的object可以是任何的對(duì)象實(shí)例,比如this,Integer.MAX_VALUE這樣的對(duì)象實(shí)例,都可以 |
| 代碼塊 | 指定的類(lèi)對(duì)象 | syncrhonized(Demo.class){......} |
使用了synchronized關(guān)鍵字之后,就可以給相應(yīng)的方法/代碼塊加上鎖,保證在一個(gè)JVM中,同時(shí)只有一個(gè)線程能夠執(zhí)行這個(gè)方法/這個(gè)代碼。但是我們并沒(méi)有看到加鎖和釋放鎖的操作,因此又被稱為“隱式鎖”。
synchronized的這個(gè)功能是在JVM中通過(guò)使用monitor來(lái)實(shí)現(xiàn)的。
2.1 sychronized對(duì)應(yīng)的字節(jié)碼
我們先來(lái)看看下面的代碼:
public class DemoOnInstance {
public int value = 0;
public int addAndGet(int increment){
synchronized (this){
value = value + increment;
return value;
}
}
}
在上面的代碼中的addAndGet方法里面,我們使用了synchronized關(guān)鍵字標(biāo)記了一個(gè)同步代碼塊,并且是將鎖加在了當(dāng)前實(shí)例this上。那么,JVM是怎么幫我們實(shí)現(xiàn)鎖的呢。我們可以看看生成的字節(jié)碼。
我們可以在編譯后的class文件上使用javap命令來(lái)查看字節(jié)碼,javap -v DemoOnInstance.class。得到的結(jié)果會(huì)包含下面的片段,這個(gè)片段是addAndGet方法對(duì)應(yīng)的字節(jié)碼。
public int addAndGet(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=2
0: aload_0
1: dup
2: astore_2
3: monitorenter
4: aload_0
5: aload_0
6: getfield #2 // Field value:I
9: iload_1
10: iadd
11: putfield #2 // Field value:I
14: aload_0
15: getfield #2 // Field value:I
18: aload_2
19: monitorexit
20: ireturn
21: astore_3
22: aload_2
23: monitorexit
24: aload_3
25: athrow
Exception table:
from to target type
4 20 21 any
21 24 21 any
上面的字節(jié)碼中,請(qǐng)注意標(biāo)記行號(hào)為3和19的兩行,分別為monitorenter和monitorexit。這兩行指令告訴JVM要獲取Monitor和釋放Monitor。 我們可以看到第23行也有monitorexit,這個(gè)是異常情況下的釋放monitor的處理。因此無(wú)論代碼是正常執(zhí)行還是異常執(zhí)行,都會(huì)執(zhí)行monitorexit,保證鎖會(huì)被釋放。
當(dāng)synchronized關(guān)鍵字應(yīng)用在方法上的時(shí)候,情況略有不同。
public class DemoOnInstanceMethod {
private int value = 0;
public synchronized int addAndGet(int increment){
value = value + increment;
return value;
}
}
上面的代碼中的addAndGet方法編譯后的字節(jié)碼如下:
public synchronized int addAndGet(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: aload_0
2: getfield #2 // Field value:I
5: iload_1
6: iadd
7: putfield #2 // Field value:I
10: aload_0
11: getfield #2 // Field value:I
14: ireturn
在字節(jié)碼中,沒(méi)有看到monitor相關(guān)的指令,但是在方法的flags中,有ACC_SYNCHRONIZED這個(gè)值。JVM在執(zhí)行該方法的時(shí)候,如果有這個(gè)flag,就會(huì)獲取鎖,方法退出時(shí)(無(wú)論是正常退出還是異常退出)釋放鎖。
2.2 有了字節(jié)碼之后呢?
synchronize關(guān)鍵字在不同的位置會(huì)生成不同的字節(jié)碼,JVM在執(zhí)行時(shí)會(huì)添加獲取monitor和釋放monitor的操作。 前面我們提到了,不同的代碼鎖定的對(duì)象是不一樣的。那么這個(gè)是怎么實(shí)現(xiàn)的呢?
前面也提到了,不同的代碼方式,鎖存在的位置是不一樣的,這是因?yàn)樵贘ava中,對(duì)應(yīng) synchronized 有兩種鎖:對(duì)象鎖和類(lèi)鎖。
對(duì)象鎖:在非靜態(tài)方法上使用 synchronized 關(guān)鍵字,或者使用 synchronized(objectInstance)這樣的方式來(lái)創(chuàng)建同步代碼塊,使用的是對(duì)象鎖。每個(gè)對(duì)象實(shí)例都有一個(gè)對(duì)象鎖,不同的對(duì)象實(shí)例各自有各自的對(duì)象鎖。對(duì)象鎖是線程可重入的,因此在同一個(gè)線程中,可以在一個(gè)同步方法中調(diào)用另外一個(gè)同步方法。但是,一個(gè)線程在執(zhí)行一個(gè)實(shí)例上的同步代碼,其他線程如果要執(zhí)行同樣實(shí)例上的同步代碼,無(wú)論是不是相同的代碼段,都會(huì)被阻塞。如果兩個(gè)線程執(zhí)行的是同一個(gè)類(lèi)的不同實(shí)例對(duì)象的同步代碼塊,則可以同時(shí)執(zhí)行。
類(lèi)鎖:在靜態(tài)方法上使用 synchronized 關(guān)鍵字,或者使用 synchronized(Demo.class)這樣的方法創(chuàng)建的同步代碼塊,使用的就是類(lèi)鎖。一個(gè)類(lèi)只有一個(gè)類(lèi)鎖(感覺(jué)類(lèi)似靜態(tài)變量)。同樣的,使用同一個(gè)類(lèi)鎖的同步代碼段,同一時(shí)間只有一個(gè)能執(zhí)行。
類(lèi)鎖和對(duì)象鎖互不干擾。
2.3 鎖的升級(jí)
新創(chuàng)建的類(lèi)鎖或者對(duì)象鎖,都是處于偏向鎖的狀態(tài)。在使用過(guò)程中,隨著競(jìng)爭(zhēng)的情況,會(huì)逐步升級(jí)為輕量鎖狀態(tài)或者重量鎖狀態(tài)。這一點(diǎn)在上一篇文章中已經(jīng)講過(guò)了,就不再贅述。
2.4 小結(jié)
synchronized 關(guān)鍵字會(huì)使用“隱式鎖”,這個(gè)是由字節(jié)碼和JVM共同來(lái)實(shí)現(xiàn)的,因此這個(gè)鎖是“本地鎖”,只能應(yīng)用于同一個(gè)JVM中的不同進(jìn)程,不能應(yīng)用于不同的JVM之間。
不同的 synchronized 的用法使用的鎖不盡相同,有對(duì)象鎖和類(lèi)鎖兩種。對(duì)象鎖和類(lèi)鎖都是可重入鎖和獨(dú)享鎖,同時(shí)也都是非公平鎖。