如噩夢(mèng)一樣的考試結(jié)束了,讓我們剖析一下Synchronized吧??
為了了解java里這個(gè)元老---Synchronized脯颜,我們知道他的幾種使用方式
- 同步普通方法蝶防,鎖的是當(dāng)前對(duì)象奏属。
public synchronized void SimpleMethod() {
// code
}
- 同步靜態(tài)方法福压,鎖的是當(dāng)前 Class 對(duì)象。
public static synchronized void staticMethod() {
// code
}
- 同步塊锐朴,鎖的是 () 中的對(duì)象兴喂。
public void Lock() {
Object o = new Object();
Synchronized (o) {
// code
}
}
寫(xiě)一段簡(jiǎn)單的代碼試試看,底層到底干了什么焚志。
public class TestSynchronize {
public static void main(String[] args) {
synchronized (TestSynchronize.class) {
testSynchronize();
}
}
public synchronized static void testSynchronize() {
System.out.println("hello synchroinze");
}
}
臨界區(qū):就是同時(shí)只允許一個(gè)線程訪問(wèn)的代碼區(qū)域衣迷,那么synchronized修飾代碼區(qū)域,就是臨界區(qū)
先用javap看一下酱酬,jvm對(duì)synchronize的處理
從紅色圈圈可以看到壶谒,JVM對(duì)于synchronize,指令級(jí)別增加了monitorenter和moniterexit岳悟。
訪問(wèn)synchronize修飾的代碼塊佃迄,通過(guò)對(duì)象監(jiān)視器( Monitor )進(jìn)行獲取泼差,而這個(gè)獲取過(guò)程排除了其他線程進(jìn)入贵少,保證了同一時(shí)間只有一個(gè)線程來(lái)訪問(wèn)。而沒(méi)有獲取到鎖的線程將會(huì)阻塞到synchronize開(kāi)始處堆缘,直到獲取鎖的線程 monitor.exit 之后才能?chē)L試?yán)^續(xù)獲取鎖滔灶。
對(duì)象監(jiān)視器(Monitor)
Java虛擬機(jī)給每個(gè)對(duì)象和class字節(jié)碼都設(shè)置了對(duì)象監(jiān)聽(tīng)器Monitor,每個(gè)對(duì)象都可以被監(jiān)視吼肥。同時(shí)在Object類中還提供了notify和wait方法來(lái)對(duì)線程進(jìn)行控制录平。
Monitor機(jī)制:
Monitor保證每次只能有一個(gè)線程能進(jìn)入這個(gè)房間進(jìn)行訪問(wèn)被保護(hù)的數(shù)據(jù)麻车,數(shù)據(jù)進(jìn)入房間即為持有Monitor,退出房間即為釋放Monitor斗这。
當(dāng)一個(gè)線程需要訪問(wèn)受保護(hù)的數(shù)據(jù)(即需要獲取對(duì)象的Monitor)時(shí)动猬,它會(huì)首先在entry-set入口隊(duì)列中排隊(duì),如果沒(méi)有其他線程正在持有對(duì)象的Monitor表箭,那么它會(huì)和entry-set隊(duì)列和wait-set隊(duì)列中的被喚醒的其他線程進(jìn)行競(jìng)爭(zhēng)(即通過(guò)CPU調(diào)度)赁咙,選出一個(gè)線程來(lái)獲取對(duì)象的Monitor,執(zhí)行受保護(hù)的代碼段免钻,執(zhí)行完畢后釋放Monitor彼水,如果已經(jīng)有線程持有對(duì)象的Monitor,那么需要等待其釋放Monitor后再進(jìn)行競(jìng)爭(zhēng)极舔。
wait-set:當(dāng)一個(gè)線程擁有Monitor后凤覆,經(jīng)過(guò)某些條件的判斷,這個(gè)時(shí)候需要調(diào)用Object的wait方法拆魏,線程就釋放了Monitor盯桦,進(jìn)入wait-set隊(duì)列,等待Object的notify方法渤刃。當(dāng)該對(duì)象調(diào)用了notify方法 或者notifyAll方法后俺附,wait-set中的線程就會(huì)被喚醒,然后在wait-set隊(duì)列中被喚醒的線程和entry-set隊(duì)列中的線程一起通過(guò)CPU調(diào)度來(lái)競(jìng)爭(zhēng)對(duì)象的Monitor溪掀,最終只有一個(gè)線程能獲取對(duì)象的Monitor事镣。
Object類wait和notify
這里Object提供了Object.wait()和Object.notify()
- wait方法
wait有三個(gè)重載方法,分別如下:
wait()
wait(long millis)
wait(long millis, int nanos)
后面兩個(gè)傳入了時(shí)間參數(shù)(nanos表示納秒)揪胃,表示如果指定時(shí)間過(guò)去還沒(méi)有其他線程調(diào)用notify或者notifyAll方法來(lái)將其喚醒璃哟,那么該線程會(huì)自動(dòng)被喚醒。
當(dāng)前線程必須獲取到了obj的Monitor喊递,調(diào)用其obj.wait()随闪,即wait必須放在同步方法或同步代碼塊中。執(zhí)行wait方法后骚勘,線程進(jìn)入等待狀態(tài)铐伴。
-
notify方法
notify:只能喚醒一個(gè)正在等待這個(gè)對(duì)象的monitor的線程
notifyAll:會(huì)喚醒所有正在等待這個(gè)對(duì)象的monitor的線程調(diào)用notify方法,必須要等同步代碼塊結(jié)束后才會(huì)釋放Monitor,必須保證其他線程處于wait狀態(tài)俏讹,否則調(diào)用notify沒(méi)有任何效果当宴。也就是wait和notify/notifyAll必須配合使用。
Java對(duì)象頭
Synchronized用的鎖是存在Java對(duì)象頭里的泽疆,這里就需要了解對(duì)象頭的結(jié)構(gòu)
java對(duì)象頭有以下兩種(32位JVM):
- 普通對(duì)象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
- 數(shù)組對(duì)象
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
對(duì)象頭的組成:
- Mark Word
存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)(hashcode,gc分代年齡)户矢,大小為JVM一個(gè)字的大小,(32bit/32位虛擬機(jī)殉疼,64bit/64位虛擬機(jī))梯浪,其中后兩位是標(biāo)記位捌年,標(biāo)記位不同,這個(gè)markword表示的含義不同
biased_lock | lock | 狀態(tài) |
---|---|---|
0 | 01 | 無(wú)鎖 |
1 | 01 | 偏向鎖 |
0 | 00 | 輕量級(jí)鎖 |
0 | 10 | 重量級(jí)鎖 |
0 | 11 | GC標(biāo)記 |
不同情況對(duì)應(yīng)的Mark Word如下
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
普通對(duì)象
identity_hashcode:25位的對(duì)象標(biāo)識(shí)Hash碼挂洛,采用延遲加載技術(shù)礼预。調(diào)用方法System.identityHashCode()計(jì)算,并會(huì)將結(jié)果寫(xiě)到該對(duì)象頭中虏劲。當(dāng)對(duì)象被鎖定時(shí)逆瑞,該值會(huì)移動(dòng)到管程Monitor中。
age:4位的Java對(duì)象年齡伙单。在GC中获高,如果對(duì)象在Survivor區(qū)復(fù)制一次,年齡增加1吻育。當(dāng)對(duì)象達(dá)到設(shè)定的閾值時(shí)念秧,將會(huì)晉升到老年代
biased_lock:對(duì)象是否啟用偏向鎖標(biāo)記,只占1個(gè)二進(jìn)制位布疼。為1時(shí)表示對(duì)象啟用偏向鎖摊趾,為0時(shí)表示對(duì)象沒(méi)有偏向鎖。偏向鎖
thread:持有偏向鎖的線程ID游两。
epoch:偏向時(shí)間戳砾层。輕量級(jí)鎖
ptr_to_lock_record:指向棧中鎖記錄的指針。重量級(jí)鎖
ptr_to_heavyweight_monitor:指向管程Monitor的指針贱案。
PS:今天不早了肛炮,明天繼續(xù)分析Synchronized,四種鎖的狀態(tài)如何切換??