Java的wait()喉脖、notify()學(xué)習(xí)三部曲由三篇文章組成亚隙,內(nèi)容分別是:?
一、通過(guò)閱讀openjdk8的源碼肠牲,分析和理解wait幼衰,notify在JVM中的具體執(zhí)行過(guò)程;?
二缀雳、修改JVM源碼渡嚣,編譯構(gòu)建成新的JVM,把我們感興趣的參數(shù)打印出來(lái),結(jié)合具體代碼檢查和我們的理解是否一致严拒;?
三扬绪、修改JVM源碼,編譯構(gòu)建成新的JVM裤唠,按照我們的理解去修改關(guān)鍵參數(shù),看能否達(dá)到預(yù)期效果莹痢;
現(xiàn)在种蘸,咱們一起開(kāi)始既漫長(zhǎng)又深入的wait、notify學(xué)習(xí)之旅吧竞膳!
Java多線程開(kāi)發(fā)中航瞭,我們常用到wait()和notify()方法來(lái)實(shí)現(xiàn)線程間的協(xié)作,簡(jiǎn)單的說(shuō)步驟如下:?
1. A線程取得鎖坦辟,執(zhí)行wait()刊侯,釋放鎖;?
2. B線程取得鎖,完成業(yè)務(wù)后執(zhí)行notify()锉走,再釋放鎖;?
3. B線程釋放鎖之后滨彻,A線程取得鎖,繼續(xù)執(zhí)行wait()之后的代碼挪蹭;
通常亭饵,對(duì)于synchronize(lock){…}這樣的代碼塊,編譯后會(huì)生成monitorenter和monitorexit指令梁厉,線程執(zhí)行到monitorenter指令時(shí)會(huì)嘗試取得lock對(duì)應(yīng)的monitor的所有權(quán)(CAS設(shè)置對(duì)象頭)辜羊,取得后即獲取到鎖,執(zhí)行monitorexit指令時(shí)會(huì)釋放monitor的所有權(quán)即釋放鎖词顾;
為了深入學(xué)習(xí)wait()和notify()八秃,先用完整的demo程序來(lái)模擬場(chǎng)景吧,以下是源碼:
publicclassNotifyDemo{privatestaticvoidsleep(longsleepVal){try{? ? ? ? ? ? Thread.sleep(sleepVal);? ? ? ? }catch(Exception e){? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }privatestaticvoidlog(String desc){? ? ? ? System.out.println(Thread.currentThread().getName() +" : "+ desc);? ? }? ? Object lock =newObject();publicvoidstartThreadA(){newThread(() -> {synchronized(lock){? ? ? ? ? ? ? ? log("get lock");? ? ? ? ? ? ? ? startThreadB();? ? ? ? ? ? ? ? log("start wait");try{? ? ? ? ? ? ? ? ? ? lock.wait();? ? ? ? ? ? ? ? }catch(InterruptedException e){? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? log("get lock after wait");? ? ? ? ? ? ? ? log("release lock");? ? ? ? ? ? }? ? ? ? },"thread-A").start();? ? }publicvoidstartThreadB(){newThread(()->{synchronized(lock){? ? ? ? ? ? ? ? log("get lock");? ? ? ? ? ? ? ? startThreadC();? ? ? ? ? ? ? ? sleep(100);? ? ? ? ? ? ? ? log("start notify");? ? ? ? ? ? ? ? lock.notify();? ? ? ? ? ? ? ? log("release lock");? ? ? ? ? ? }? ? ? ? },"thread-B").start();? ? }publicvoidstartThreadC(){newThread(() -> {synchronized(lock){? ? ? ? ? ? ? ? log("get lock");? ? ? ? ? ? ? ? log("release lock");? ? ? ? ? ? }? ? ? ? },"thread-C").start();? ? }publicstaticvoidmain(String[] args){newNotifyDemo().startThreadA();? ? }}
以上就是本次實(shí)戰(zhàn)用到的demo肉盹,代碼功能簡(jiǎn)述如下:
啟動(dòng)線程A昔驱,取得鎖之后先啟動(dòng)線程B再執(zhí)行wait()方法,釋放鎖并等待垮媒;
線程B啟動(dòng)之后會(huì)等待鎖舍悯,A線程執(zhí)行wait()之后,線程B取得鎖睡雇,然后啟動(dòng)線程C萌衬,再執(zhí)行notify喚醒線程A,最后退出synchronize代碼塊它抱,釋放鎖;
線程C啟動(dòng)之后就一直在等待鎖秕豫,這時(shí)候線程B還沒(méi)有退出synchronize代碼塊,鎖還在線程B手里;
線程A在線程B執(zhí)行notify()之后就一直在等待鎖混移,這時(shí)候線程B還沒(méi)有退出synchronize代碼塊祠墅,鎖還在線程B手里;
線程B退出synchronize代碼塊歌径,釋放鎖之后毁嗦,線程A和線程C競(jìng)爭(zhēng)鎖;
把上面的代碼在Openjdk8下面執(zhí)行回铛,反復(fù)執(zhí)行多次狗准,都得到以下結(jié)果:
thread-A : get lock
thread-A : start wait
thread-B : get lock
thread-C : c thread is start
thread-B : start notify
thread-B : release lock
thread-A : after wait, acquire lock again
thread-A : release lock
thread-C : get lock
thread-C : release lock
1
2
3
4
5
6
7
8
9
10
針對(duì)以上結(jié)果,問(wèn)題來(lái)了:?
第一個(gè)問(wèn)題:?
將以上代碼反復(fù)執(zhí)行多次茵肃,結(jié)果都是B釋放鎖之后A會(huì)先得到鎖腔长,這又是為什么呢?C為何不能先拿到鎖呢验残?
第二個(gè)問(wèn)題:?
線程C自開(kāi)始就執(zhí)行了monitorenter指令捞附,它能得到鎖是容易理解的,但是線程A呢您没?在wait()之后并沒(méi)有沒(méi)有monitorenter指令鸟召,那么它又是如何取得鎖的呢?
wait()紊婉、notify()這些方法都是native方法药版,所以只有從JVM源碼尋找答案了,本次閱讀的是openjdk8的源碼喻犁;
按照demo代碼執(zhí)行順序槽片,我整理了如下問(wèn)題,帶著這些問(wèn)題去看JVM源碼可以聚焦主線肢础,不要被一些支線的次要的代碼卡住(例如一些異常處理还栓,監(jiān)控和上報(bào)等):?
1. 線程A在wait()的時(shí)候做了什么??
2. 線程C啟動(dòng)后传轰,由于此時(shí)線程B持有鎖剩盒,那么線程C此時(shí)在干啥??
3. 線程B在notify()的時(shí)候做了什么慨蛙??
4. 線程B釋放鎖的時(shí)候做了什么辽聊?
在源碼中有段注釋堪稱是整篇文章最重要的說(shuō)明,請(qǐng)大家始終記住這段信息期贫,處處都用得上:
ObjectWaiter對(duì)象存在于WaitSet跟匆、EntryList、cxq等集合中通砍,或者正在這些集合中移動(dòng)
原文如下:
請(qǐng)務(wù)必記住這三個(gè)集合:WaitSet玛臂、EntryList烤蜕、cxq
好了,接下來(lái)看源碼分析問(wèn)題吧:
打開(kāi)hotspot/src/share/vm/runtime/objectMonitor.cpp,看ObjectMonitor::wait方法:
如上圖所示迹冤,有兩處代碼值得我們注意:?
1. 綠框中將當(dāng)前線程包裝成ObjectWaiter對(duì)象讽营,并且狀態(tài)為T(mén)S_WAIT,這里對(duì)應(yīng)的是jstack看到的線程狀態(tài)WAITING泡徙;?
2. 紅框中調(diào)用了AddWaiter方法橱鹏,跟進(jìn)去看下:
這個(gè)ObjectWaiter對(duì)象被放入了_WaitSet中,_WaitSet是個(gè)環(huán)形雙向鏈表(circular doubly linked list)
回到ObjectMonitor::wait方法接著往下看堪藐,會(huì)發(fā)現(xiàn)關(guān)鍵代碼如下圖蚀瘸,當(dāng)前線程通過(guò)park()方法開(kāi)始掛起(suspend):
至此,我們把wait()方法要做的事情就理清了:?
1. 包裝成ObjectWaiter對(duì)象庶橱,狀態(tài)為T(mén)S_WAIT贪惹;?
2. ObjectWaiter對(duì)象被放入_WaitSet中苏章;?
3. 當(dāng)前線程掛起;
此時(shí)的線程C無(wú)法進(jìn)入synchronized{}代碼塊奏瞬,用jstack看應(yīng)該是BLOCKED狀態(tài)枫绅,如下圖:
我們看看monitorenter指令對(duì)應(yīng)的源碼吧,位置:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread*thread, BasicObjectLock*elem))#ifdefASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);#endifif(PrintBiasedLockingStatistics) {? ? Atomic::inc(BiasedLocking::slow_path_entry_count_addr());? }Handleh_obj(thread, elem->obj());? assert(Universe::heap()->is_in_reserved_or_null(h_obj()),"must be NULL or an object");if(UseBiasedLocking) {// Retry fast entry if bias is revoked to avoid unnecessary inflationObjectSynchronizer::fast_enter(h_obj, elem->lock(),true, CHECK);? }else{? ? ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);? }? assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),"must be NULL or an object");#ifdefASSERTthread->last_frame().interpreter_frame_verify_monitor(elem);#endifIRT_END
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面的代碼有個(gè)if (UseBiasedLocking)判斷硼端,是判斷是否使用偏向鎖的并淋,本例中的鎖顯然已經(jīng)不屬于當(dāng)前線程C了,所以我們還是直接看slow_enter(h_obj, elem->lock(), CHECK)方法吧珍昨;
打開(kāi)openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp:
voidObjectSynchronizer::slow_enter(Handleobj, BasicLock*lock, TRAPS) {? markOop mark=obj->mark();? assert(!mark->has_bias_pattern(),"should not see bias pattern here");//是否處于無(wú)鎖狀態(tài)if(mark->is_neutral()) {// Anticipate successful CAS -- the ST of the displaced mark must// be visible <= the ST performed by the CAS.lock->set_displaced_header(mark);//無(wú)鎖狀態(tài)就去競(jìng)爭(zhēng)鎖if(mark==(markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {? ? ? TEVENT (slow_enter: release stacklock) ;return;? ? }// Fall through to inflate() ...}elseif(mark->has_locker()&&THREAD->is_lock_owned((address)mark->locker())) {//如果處于有鎖狀態(tài)县耽,就檢查是不是當(dāng)前線程持有鎖,如果是當(dāng)前線程持有的镣典,就return兔毙,然后就能執(zhí)行同步代碼塊中的代碼了assert(lock!=mark->locker(),"must not re-lock the same lock");? ? assert(lock!=(BasicLock*)obj->mark(),"don't relock with same BasicLock");? ? lock->set_displaced_header(NULL);return;? }#if0// The following optimization isn't particularly useful.if(mark->has_monitor()&&mark->monitor()->is_entered(THREAD)) {? ? lock->set_displaced_header (NULL) ;return;? }#endif// The object header will never be displaced to this lock,// so it does not matter what the value is, except that it// must be non-zero to avoid looking like a re-entrant lock,// and must not look locked either.lock->set_displaced_header(markOopDesc::unused_mark());//鎖膨脹ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
線程C在上面代碼中的執(zhí)行順序如下:?
1. 判斷是否是無(wú)鎖狀態(tài),如果是就通過(guò)Atomic::cmpxchg_ptr去競(jìng)爭(zhēng)鎖兄春;?
2. 不是無(wú)鎖狀態(tài)澎剥,就檢查當(dāng)前鎖是否是線程C持有;?
3. 不是線程C持有赶舆,調(diào)用inflate方法開(kāi)始鎖膨脹哑姚;
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
來(lái)看看鎖膨脹的源碼:
如上圖,鎖膨脹的代碼太長(zhǎng)芜茵,我們這里只看關(guān)鍵代碼吧:?
紅框中叙量,如果當(dāng)前狀態(tài)已經(jīng)是重量級(jí)鎖,就通過(guò)mark->monitor()方法取得ObjectMonitor指針再返回夕晓;?
綠框中宛乃,如果還不是重量級(jí)鎖,就檢查是否處于膨脹中狀態(tài)(其他線程正在膨脹中),如果是膨脹中征炼,就調(diào)用ReadStableMark方法進(jìn)行等待析既,ReadStableMark方法執(zhí)行完畢后再通過(guò)continue繼續(xù)檢查,ReadStableMark方法中還會(huì)調(diào)用os::NakedYield()釋放CPU資源谆奥;
如果紅框和綠框的條件都沒(méi)有命中眼坏,目前已經(jīng)是輕量級(jí)鎖了(不是重量級(jí)鎖并且不處于鎖膨脹狀態(tài)),可以開(kāi)始膨脹了酸些,如下圖:
簡(jiǎn)單來(lái)說(shuō)宰译,鎖膨脹就是通過(guò)CAS將監(jiān)視器對(duì)象OjectMonitor的狀態(tài)設(shè)置為INFLATING,如果CAS失敗魄懂,就在此循環(huán)沿侈,再走前一副圖中的的紅框和綠框中的判斷,如果CAS設(shè)置成功市栗,會(huì)繼續(xù)設(shè)置ObjectMonitor中的header缀拭、owner等字段,然后inflate方法返回監(jiān)視器對(duì)象OjectMonitor填帽;
看看之前slow_enter方法中蛛淋,調(diào)用inflate方法的代碼如下:
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
1
所以inflate方法返回監(jiān)視器對(duì)象OjectMonitor之后,會(huì)立刻執(zhí)行OjectMonitor的enter方法篡腌,這個(gè)方法中開(kāi)始競(jìng)爭(zhēng)鎖了褐荷,方法在openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp文件中:
如上圖,紅框中表示OjectMonitor的enter方法一進(jìn)來(lái)就通過(guò)CAS將OjectMonitor的_owner設(shè)置為當(dāng)前線程嘹悼,綠框中表示設(shè)置成功的邏輯叛甫,第一個(gè)if表示重入鎖的邏輯,第二個(gè)if表示第一次設(shè)置_owner成功绘迁,都意味著競(jìng)爭(zhēng)鎖成功合溺,而我們的線程C顯然是競(jìng)爭(zhēng)失敗的,會(huì)進(jìn)入下圖中的無(wú)線循環(huán)缀台,反復(fù)調(diào)用EnterI方法:
進(jìn)入EnterI方法看看:
如上圖棠赛,首先構(gòu)造一個(gè)ObjectWaiter對(duì)象node,后面的for(;;)代碼塊中來(lái)是一段非常巧妙的代碼膛腐,同一時(shí)刻可能有多個(gè)線程都競(jìng)爭(zhēng)鎖失敗走進(jìn)這個(gè)EnterI方法睛约,所以在這個(gè)for循環(huán)中,用CAS將_cxq地址放入node的_next哲身,也就是把node放到_cxq隊(duì)列的首位辩涝,如果CAS失敗,就表示其他線程把node放入到_cxq的首位了勘天,所以通過(guò)for循環(huán)再放一次怔揩,只要成功捉邢,此node就一定在最新的_cxq隊(duì)列的首位。
接下來(lái)的代碼又是一個(gè)無(wú)限循環(huán)商膊,如下圖:
從上圖可以看出伏伐,進(jìn)入循環(huán)后先調(diào)用TryLock方法競(jìng)爭(zhēng)一次鎖,如果成功了就退出循環(huán)晕拆,否則就調(diào)用Self->_ParkEvent->park方法使線程掛起岛杀,這里有自旋鎖的邏輯速妖,也就是park方法帶了時(shí)間參數(shù),就會(huì)在掛起一段時(shí)間后自動(dòng)喚醒兼贸,如果不是自旋的條件弧圆,就一直掛起等待被其他條件喚醒谨胞,線程被喚醒后又會(huì)執(zhí)行TryLock方法競(jìng)爭(zhēng)一次鎖榄棵,競(jìng)爭(zhēng)不到繼續(xù)這個(gè)for循環(huán)揩晴;
到這里我們已經(jīng)把線程C在BLOCK的時(shí)候的邏輯理清楚了,小結(jié)如下:
偏向鎖邏輯整吆,未命中未舟;
如果是無(wú)鎖狀態(tài),就通過(guò)CAS去競(jìng)爭(zhēng)鎖掂为,此處由于鎖已經(jīng)被線程B持有,所以不是無(wú)鎖狀態(tài)员串;
不是無(wú)鎖狀態(tài)勇哗,而且鎖不是線程C持有,執(zhí)行鎖膨脹寸齐,構(gòu)造OjectMonitor對(duì)象欲诺;
競(jìng)爭(zhēng)鎖,競(jìng)爭(zhēng)失敗就將線程加入_cxq隊(duì)列的首位渺鹦;
開(kāi)始無(wú)限循環(huán)扰法,競(jìng)爭(zhēng)鎖成功就退出循環(huán),競(jìng)爭(zhēng)失敗線程掛起毅厚,等待被喚醒后繼續(xù)競(jìng)爭(zhēng)塞颁;
接下來(lái)該線程B執(zhí)行notify了,代碼是objectMonitor.cpp的ObjectMonitor::notify方法:
如上圖所示吸耿,首先是Policy的賦值祠锣,其次是調(diào)用DequeueWaiter()方法將_WaitSet隊(duì)列的第一個(gè)值取出并返回,還記得_WaitSet么咽安?所有wait的線程都被包裝成ObjectWaiter對(duì)象然后放進(jìn)來(lái)了伴网;?
接下來(lái)對(duì)ObjectWaiter對(duì)象的處理方式,根據(jù)Policy的不同而不同:?
Policy == 0:放入_EntryList隊(duì)列的排頭位置妆棒;?
Policy == 1:放入_EntryList隊(duì)列的末尾位置澡腾;?
Policy == 2:_EntryList隊(duì)列為空就放入_EntryList沸伏,否則放入_cxq隊(duì)列的排頭位置;
如上圖所示动分,請(qǐng)注意把ObjectWaiter的地址寫(xiě)到_cxq變量的時(shí)候要用CAS操作毅糟,因?yàn)榇藭r(shí)可能有其他線程正在競(jìng)爭(zhēng)鎖,競(jìng)爭(zhēng)失敗的時(shí)候會(huì)將自己包裝成ObjectWaiter對(duì)象加入到_cxq中刺啦;
這里的代碼有一處疑問(wèn)留特,期待著讀著您的指教:如果_EntryList為空,就把ObjectWaiter放入ObjectWaiter中玛瘸,為什么要這樣做呢蜕青?
Policy == 3:放入_cxq隊(duì)列中,末尾位置糊渊;更新_cxq變量的值的時(shí)候右核,同樣要通過(guò)CAS注意并發(fā)問(wèn)題;
這里有一段很巧妙的代碼渺绒,現(xiàn)將_cxq保存在Tail中贺喝,正常情況下將ObjectWaiter賦值給Tail->_next就可以了,但是此時(shí)有可能其他線程正在_cxq的尾部追加數(shù)據(jù)了宗兼,所以此時(shí)Tail對(duì)象對(duì)應(yīng)的記錄就不是最后一條了躏鱼,那么它的_next就非空了,一旦發(fā)生這種情況殷绍,就執(zhí)行Tail = Tail->_next染苛,這樣就獲得了最新的_cxq的尾部數(shù)據(jù),如下圖所示:
Policy等于其他值主到,立即喚醒ObjectWaiter對(duì)應(yīng)的線程茶行;
小結(jié)一下,線程B執(zhí)行notify時(shí)候做的事情:
執(zhí)行過(guò)wait的線程都在隊(duì)列_WaitSet中登钥,此處從_WaitSet中取出第一個(gè)畔师;
根據(jù)Policy的不同,將這個(gè)線程放入_EntryList或者_(dá)cxq隊(duì)列中的起始或末尾位置牧牢;
接下來(lái)到了揭開(kāi)問(wèn)題的關(guān)鍵了看锉,我們來(lái)看objectMonitor.cpp的ObjectMonitor::exit方法;
如上圖塔鳍,方法一進(jìn)來(lái)先做一些合法性判斷度陆,接下來(lái)如紅框所示,是偏向鎖邏輯献幔,偏向次數(shù)減一后直接返回懂傀,顯然線程B在此處不會(huì)返回,而是繼續(xù)往下執(zhí)行蜡感;
根據(jù)QMode的不同蹬蚁,有不同的處理方式:?
1. QMode = 2恃泪,并且_cxq非空:取_cxq隊(duì)列排頭位置的ObjectWaiter對(duì)象,調(diào)用ExitEpilog方法犀斋,該方法會(huì)喚醒ObjectWaiter對(duì)象的線程贝乎,此處會(huì)立即返回,后面的代碼不會(huì)執(zhí)行了叽粹;?
2. QMode = 3览效,并且_cxq非空:把_cxq隊(duì)列首元素放入_EntryList的尾部;?
3. QMode = 4虫几,并且_cxq非空:把_cxq隊(duì)列首元素放入_EntryList的頭部锤灿;?
4. QMode = 0,不做什么辆脸,繼續(xù)往下看但校;
只有QMode=2的時(shí)候會(huì)提前返回,等于0啡氢、3状囱、4的時(shí)候都會(huì)繼續(xù)往下執(zhí)行:
如果_EntryList的首元素非空,就取出來(lái)調(diào)用ExitEpilog方法倘是,該方法會(huì)喚醒ObjectWaiter對(duì)象的線程亭枷,然后立即返回;?
如果_EntryList的首元素為空搀崭,就取_cxq的首元素奶栖,放入_EntryList,然后再?gòu)腳EntryList中取出來(lái)執(zhí)行ExitEpilog方法门坷,然后立即返回;
以上操作袍镀,均是執(zhí)行過(guò)ExitEpilog方法然后立即返回默蚌,如果取出的元素為空,就執(zhí)行循環(huán)繼續(xù)任邸绸吸;
小結(jié)一下,線程B釋放了鎖之后设江,執(zhí)行的操作如下:?
1. 偏向鎖邏輯锦茁,此處未命中;?
2. 根據(jù)QMode的不同叉存,將ObjectWaiter從_cxq或者_(dá)EntryList中取出后喚醒码俩;?
3. 喚醒的元素會(huì)繼續(xù)執(zhí)行掛起前的代碼,按照我們之前的分析歼捏,線程喚醒后稿存,就會(huì)通過(guò)CAS去競(jìng)爭(zhēng)鎖笨篷,此時(shí)由于線程B已經(jīng)釋放了鎖,那么此時(shí)應(yīng)該能競(jìng)爭(zhēng)成功瓣履;
到了現(xiàn)在已經(jīng)將之前的幾個(gè)問(wèn)題搞清了率翅,匯總起來(lái)看看:?
1. 線程A在wait() 后被加入了_WaitSet隊(duì)列中;?
2. 線程C被線程B啟動(dòng)后競(jìng)爭(zhēng)鎖失敗袖迎,被加入到_cxq隊(duì)列的首位冕臭;?
3. 線程B在notify()時(shí),從_WaitSet中取出第一個(gè)燕锥,根據(jù)Policy的不同辜贵,將這個(gè)線程放入_EntryList或者_(dá)cxq隊(duì)列中的起始或末尾位置;?
4. 根據(jù)QMode的不同脯宿,將ObjectWaiter從_cxq或者_(dá)EntryList中取出后喚醒念颈;;
所以连霉,最初的問(wèn)題已經(jīng)清楚了榴芳,wait()的線程被喚醒后,會(huì)進(jìn)入一個(gè)隊(duì)列跺撼,然后JVM會(huì)根據(jù)Policy和QMode的不同對(duì)隊(duì)列中的ObjectWaiter做不同的處理窟感,被選中的ObjectWaiter會(huì)被喚醒,去競(jìng)爭(zhēng)鎖歉井;
至此柿祈,源碼分析已結(jié)束,但是因?yàn)槲覀儾恢繮olicy和QMode參數(shù)到底是多少哩至,所以還不能對(duì)之前的問(wèn)題有個(gè)明確的結(jié)果躏嚎,這些還是留在下一章來(lái)解答吧,下一章里我們?nèi)バ薷腏VM源碼菩貌,把參數(shù)都打印出來(lái)卢佣;