java程序中我們可以使用synchronized關(guān)鍵字對程序加鎖,它可以保證方法或者代碼塊運行時同一時刻只有一個方法可以進入到臨界區(qū)域,同時它還可以保證共享變量的內(nèi)存可見性,synchronzied關(guān)鍵字可以聲明一個同步代碼塊,也可以用來修飾靜態(tài)方法或者實例方法突颊。當(dāng)synchronized修飾在不同地方時裆甩,它也是在對不同對象進行加鎖操作:
1.同步代碼塊:鎖是括號里面的對象
2.靜態(tài)方法:瑣是當(dāng)前類的class對象
3.實例方法:鎖是當(dāng)前實例對象
當(dāng)聲明synchronized代碼塊時冗锁,編譯而成的字節(jié)碼將會包含monitorenter和monitorexit兩個指令,這兩種指令都會消耗操作數(shù)棧上的一個引用類型的元素(代碼塊中小括號里面的對象)作為加鎖和解鎖的對象嗤栓。
public void foo(Object sync) {
synchronized (sync) {
sync.hashCode();
}
}
// 上面的 Java 代碼將編譯為下面的字節(jié)碼
public void foo(java.lang.Object);
Code:
0: aload_1
1: dup
2: astore_2
3: monitorenter
4: aload_1
5: invokevirtual java/lang/Object.hashCode:()I
8: pop
9: aload_2
10: monitorexit
11: goto 19
14: astore_3
15: aload_2
16: monitorexit
17: aload_3
18: athrow
19: return
Exception table:
from to target type
4 11 14 any
14 17 14 any
從上面的字節(jié)碼中可以看到有一個monitorenter指令和多個monitorexit指令冻河,這是因為要確保在任何情況下退出都會釋放掉鎖。關(guān)于monitorenter和monitorexit的作用茉帅,我們可以抽象的理解為每個鎖對象都擁有一個鎖的計數(shù)器和一個持有該鎖的線程指針叨叙。
當(dāng)執(zhí)行到monitorenter時如果鎖計數(shù)器個數(shù)為零則代表沒有其他線程鎖定,這時java虛擬機會將鎖對象的持有線程設(shè)置為當(dāng)前線程堪澎,并把計數(shù)器設(shè)置為1擂错。當(dāng)鎖計數(shù)器不為零時判斷持有鎖對象的線程是不是當(dāng)前線程,如果是當(dāng)前線程則把計數(shù)器加1樱蛤。(因為synchronized是可重入鎖)當(dāng)執(zhí)行到monitorexit時钮呀,jvm會將計數(shù)器個數(shù)減1。當(dāng)計數(shù)器等于零的時候代表鎖已經(jīng)釋放昨凡。
當(dāng)synchronized標(biāo)記方法時爽醋,在字節(jié)碼中flags包括ACC_SYNCHRONIZED。此標(biāo)記標(biāo)識進入該方法時java虛擬機要進行monitorenter操作便脊,在退出時進行(正常退出或者異常退出)monitorexit操作蚂四。
二 、JAVA對象頭
在java虛擬機中哪痰,每個java對象都有一個對象頭(object header)遂赠,由標(biāo)記字段(Mark Word)和類型指針(Klass Pointer)構(gòu)成。標(biāo)記字段用來存儲對象運行時Java虛擬機有關(guān)該對象的運行數(shù)據(jù)晌杰,如哈希碼解愤、GC信息、鎖狀態(tài)標(biāo)志乎莉、線程持有的鎖等等送讲。類型指針是對象指向它的類元數(shù)據(jù)的指針,Java虛擬機通過該指針確定這個對象是什么類的實例惋啃。
三哼鬓、JVM鎖優(yōu)化
重量級鎖
重量級鎖是java虛擬機中最基本的鎖的實現(xiàn)方式,在這種鎖狀態(tài)下边灭,獲取鎖失敗的線程會進入阻塞狀態(tài)异希,當(dāng)目標(biāo)鎖被釋放時喚醒阻塞線程。
java線程中的阻塞和喚醒都是依靠操作系統(tǒng)來實現(xiàn)的绒瘦,但是這種方式會有系統(tǒng)調(diào)用称簿,需要從操作系統(tǒng)的用戶狀態(tài)切換到內(nèi)核狀態(tài)扣癣,開銷很大。