[Java-多線程]“基礎(chǔ)篇”05之 線程等待與喚醒

概要
本章,會(huì)對線程等待/喚醒方法進(jìn)行介紹咽筋。涉及到的內(nèi)容包括:

  1. wait(), notify(), notifyAll()等方法介紹
  2. wait()和notify()
  3. wait(long timeout)和notify()
  4. wait() 和 notifyAll()
  5. 為什么notify(), wait()等函數(shù)定義在Object中熟尉,而不是Thread中
    轉(zhuǎn)載請注明出處:http://www.cnblogs.com/skywang12345/p/3479224.html

wait(), notify(), notifyAll()等方法介紹
在Object.Java中揭厚,定義了wait(), notify()和notifyAll()等接口币砂。wait()的作用是讓當(dāng)前線程進(jìn)入等待狀態(tài)尖飞,同時(shí)戈轿,wait()也會(huì)讓當(dāng)前線程釋放它所持有的鎖凌受。而notify()和notifyAll()的作用,則是喚醒當(dāng)前對象上的等待線程思杯;notify()是喚醒單個(gè)線程胜蛉,而notifyAll()是喚醒所有的線程。
Object類中關(guān)于等待/喚醒的API詳細(xì)信息如下:
**notify() **-- 喚醒在此對象監(jiān)視器上等待的單個(gè)線程色乾。
**notifyAll() ** -- 喚醒在此對象監(jiān)視器上等待的所有線程誊册。
**wait() ** -- 讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”,“直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法”暖璧,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)案怯。
**wait(long timeout) ** -- 讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”,“直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法漆撞,或者超過指定的時(shí)間量”殴泰,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)。
**wait(long timeout, int nanos) **-- 讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”浮驳,“直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法悍汛,或者其他某個(gè)線程中斷當(dāng)前線程,或者已超過某個(gè)實(shí)際時(shí)間量”至会,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)离咐。

2. wait()和notify()示例
下面通過示例演示"wait()和notify()配合使用的情形"。

// WaitTest.java的源碼
class ThreadA extends Thread{ 
    public ThreadA(String name) { 
        super(name); 
    } 
    public void run() { 
        synchronized (this) { 
            System.out.println(Thread.currentThread().getName()+" call notify()"); 
            // 喚醒當(dāng)前的wait線程 
            notify(); 
        } 
    }
}
public class WaitTest { 
    public static void main(String[] args) { 
        ThreadA t1 = new ThreadA("t1"); 
        synchronized(t1) { 
            try { 
                // 啟動(dòng)“線程t1”     
                System.out.println(Thread.currentThread().getName()+" start t1"); 
                t1.start(); // 主線程等待t1通過notify()喚醒。 
                System.out.println(Thread.currentThread().getName()+" wait()"); 
                t1.wait(); 
                System.out.println(Thread.currentThread().getName()+" continue"); 
            } catch (InterruptedException e) { e.printStackTrace(); } 
        } 
    }
}

運(yùn)行結(jié)果:
main start t1
main wait()
t1 call notify()
main continue

結(jié)果說明:如下圖宵蛀,說明了“主線程”和“線程t1”的流程昆著。
(01) 注意,圖中"主線程" 代表“主線程main”术陶。"線程t1" 代表WaitTest中啟動(dòng)的“線程t1”凑懂。 而“鎖” 代表“t1這個(gè)對象的同步鎖”。
(02) “主線程”通過 new ThreadA("t1") 新建“線程t1”梧宫。隨后通過synchronized(t1)獲取“t1對象的同步鎖”接谨。然后調(diào)用t1.start()啟動(dòng)“線程t1”。
(03) “主線程”執(zhí)行t1.wait() 釋放“t1對象的鎖”并且進(jìn)入“等待(阻塞)狀態(tài)”塘匣。等待t1對象上的線程通過notify() 或 notifyAll()將其喚醒脓豪。
(04) “線程t1”運(yùn)行之后,通過synchronized(this)獲取“當(dāng)前對象的鎖”忌卤;接著調(diào)用notify()喚醒“當(dāng)前對象上的等待線程”扫夜,也就是喚醒“主線程”。
(05) “線程t1”運(yùn)行完畢之后驰徊,釋放“當(dāng)前對象的鎖”笤闯。緊接著,“主線程”獲取“t1對象的鎖”辣垒,然后接著運(yùn)行望侈。


對于上面的代碼?曾經(jīng)有個(gè)朋友問到過:t1.wait()應(yīng)該是讓“線程t1”等待勋桶;但是脱衙,為什么卻是讓“主線程main”等待了呢?在解答該問題前例驹,我們先看看jdk文檔中關(guān)于wait的一段介紹:
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

中文意思大概是:
引起“當(dāng)前線程”等待捐韩,直到另外一個(gè)線程調(diào)用notify()或notifyAll()喚醒該線程。換句話說鹃锈,這個(gè)方法和wait(0)的效果一樣荤胁!(補(bǔ)充,對于wait(long millis)方法屎债,當(dāng)millis為0時(shí)仅政,表示無限等待,直到被notify()或notifyAll()喚醒)盆驹≡驳ぃ“當(dāng)前線程”在調(diào)用wait()時(shí),必須擁有該對象的同步鎖躯喇。該線程調(diào)用wait()之后辫封,會(huì)釋放該鎖硝枉;然后一直等待直到“其它線程”調(diào)用對象的同步鎖的notify()或notifyAll()方法。然后倦微,該線程繼續(xù)等待直到它重新獲取“該對象的同步鎖”妻味,然后就可以接著運(yùn)行。

注意:jdk的解釋中欣福,說wait()的作用是讓“當(dāng)前線程”等待责球,而“當(dāng)前線程”是指正在cpu上運(yùn)行的線程!這也意味著劣欢,雖然t1.wait()是通過“線程t1”調(diào)用的wait()方法棕诵,但是調(diào)用t1.wait()的地方是在“主線程main”中。而主線程必須是“當(dāng)前線程”凿将,也就是運(yùn)行狀態(tài),才可以執(zhí)行t1.wait()价脾。所以牧抵,此時(shí)的“當(dāng)前線程”是“主線程main”!因此侨把,t1.wait()是讓“主線程”等待犀变,而不是“線程t1”!

3. wait(long timeout)和notify()
wait(long timeout)會(huì)讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”秋柄,“直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法获枝,或者超過指定的時(shí)間量”,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)骇笔。下面的示例就是演示wait(long timeout)在超時(shí)情況下省店,線程被喚醒的情況。

// WaitTimeoutTest.java的源碼class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { System.out.println(Thread.currentThread().getName() + " run "); // 死循環(huán)笨触,不斷運(yùn)行懦傍。 while(true) ; }}public class WaitTimeoutTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // 啟動(dòng)“線程t1” System.out.println(Thread.currentThread().getName() + " start t1"); t1.start(); // 主線程等待t1通過notify()喚醒 或 notifyAll()喚醒,或超過3000ms延時(shí)芦劣;然后才被喚醒粗俱。 System.out.println(Thread.currentThread().getName() + " call wait "); t1.wait(3000); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } }}

運(yùn)行結(jié)果
main start
t1main call wait
t1 run // 大約3秒之后...輸出“main continue”
main continue

結(jié)果說明:如下圖,說明了“主線程”和“線程t1”的流程虚吟。
(01) 注意寸认,圖中"主線程" 代表WaitTimeoutTest主線程(即,線程main)串慰。"線程t1" 代表WaitTest中啟動(dòng)的線程t1偏塞。 而“鎖” 代表“t1這個(gè)對象的同步鎖”。
(02) 主線程main執(zhí)行t1.start()啟動(dòng)“線程t1”模庐。
(03) 主線程main執(zhí)行t1.wait(3000)烛愧,此時(shí),主線程進(jìn)入“阻塞狀態(tài)”。需要“用于t1對象鎖的線程通過notify() 或者 notifyAll()將其喚醒” 或者 “超時(shí)3000ms之后”怜姿,主線程main才進(jìn)入到“就緒狀態(tài)”慎冤,然后才可以運(yùn)行。
(04) “線程t1”運(yùn)行之后沧卢,進(jìn)入了死循環(huán)蚁堤,一直不斷的運(yùn)行。
(05) 超時(shí)3000ms之后但狭,主線程main會(huì)進(jìn)入到“就緒狀態(tài)”披诗,然后接著進(jìn)入“運(yùn)行狀態(tài)”。

4. wait() 和 notifyAll()
通過前面的示例立磁,我們知道 notify() 可以喚醒在此對象監(jiān)視器上等待的單個(gè)線程呈队。下面,我們通過示例演示notifyAll()的用法唱歧;它的作用是喚醒在此對象監(jiān)視器上等待的所有線程宪摧。

 1 public class NotifyAllTest { 2  3 private static Object obj = new Object(); 4 public static void main(String[] args) { 5  6 ThreadA t1 = new ThreadA("t1"); 7 ThreadA t2 = new ThreadA("t2"); 8 ThreadA t3 = new ThreadA("t3"); 9  t1.start();10  t2.start();11  t3.start();12 13 try {14 System.out.println(Thread.currentThread().getName()+" sleep(3000)");15 Thread.sleep(3000);16 } catch (InterruptedException e) {17  e.printStackTrace();18  }19 20 synchronized(obj) {21 // 主線程等待喚醒。22 System.out.println(Thread.currentThread().getName()+" notifyAll()");23  obj.notifyAll();24  }25  }26 27 static class ThreadA extends Thread{28 29 public ThreadA(String name){30 super(name);31  }32 33 public void run() {34 synchronized (obj) {35 try {36 // 打印輸出結(jié)果37 System.out.println(Thread.currentThread().getName() + " wait");38 39 // 喚醒當(dāng)前的wait線程40  obj.wait();41 42 // 打印輸出結(jié)果43 System.out.println(Thread.currentThread().getName() + " continue");44 } catch (InterruptedException e) {45  e.printStackTrace();46  }47  }48  }49  }50 }

運(yùn)行結(jié)果
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue

結(jié)果說明:參考下面的流程圖颅崩。
(01) 主線程中新建并且啟動(dòng)了3個(gè)線程"t1", "t2"和"t3"几于。
(02) 主線程通過sleep(3000)休眠3秒。在主線程休眠3秒的過程中沿后,我們假設(shè)"t1", "t2"和"t3"這3個(gè)線程都運(yùn)行了沿彭。以"t1"為例,當(dāng)它運(yùn)行的時(shí)候尖滚,它會(huì)執(zhí)行obj.wait()等待其它線程通過notify()或額nofityAll()來喚醒它喉刘;相同的道理,"t2"和"t3"也會(huì)等待其它線程通過nofity()或nofityAll()來喚醒它們熔掺。
(03) 主線程休眠3秒之后饱搏,接著運(yùn)行。執(zhí)行 obj.notifyAll() 喚醒obj上的等待線程置逻,即喚醒"t1", "t2"和"t3"這3個(gè)線程推沸。 緊接著,主線程的synchronized(obj)運(yùn)行完畢之后券坞,主線程釋放“obj鎖”鬓催。這樣,"t1", "t2"和"t3"就可以獲取“obj鎖”而繼續(xù)運(yùn)行了恨锚!

5. 為什么notify(), wait()等函數(shù)定義在Object中宇驾,而不是Thread中
Object中的wait(), notify()等函數(shù),和synchronized一樣猴伶,會(huì)對“對象的同步鎖”進(jìn)行操作课舍。
wait()會(huì)使“當(dāng)前線程”等待塌西,因?yàn)榫€程進(jìn)入等待狀態(tài),所以線程應(yīng)該釋放它鎖持有的“同步鎖”筝尾,否則其它線程獲取不到該“同步鎖”而無法運(yùn)行捡需!OK,線程調(diào)用wait()之后筹淫,會(huì)釋放它鎖持有的“同步鎖”站辉;而且,根據(jù)前面的介紹损姜,我們知道:等待線程可以被notify()或notifyAll()喚醒∈伟現(xiàn)在,請思考一個(gè)問題:notify()是依據(jù)什么喚醒等待線程的摧阅?或者說汰蓉,wait()等待線程和notify()之間是通過什么關(guān)聯(lián)起來的?答案是:依據(jù)“對象的同步鎖”棒卷。
負(fù)責(zé)喚醒等待線程的那個(gè)線程(我們稱為“喚醒線程”)古沥,它只有在獲取“該對象的同步鎖”(這里的同步鎖必須和等待線程的同步鎖是同一個(gè)),并且調(diào)用notify()或notifyAll()方法之后娇跟,才能喚醒等待線程。雖然太颤,等待線程被喚醒苞俘;但是,它不能立刻執(zhí)行龄章,因?yàn)閱拘丫€程還持有“該對象的同步鎖”吃谣。必須等到喚醒線程釋放了“對象的同步鎖”之后,等待線程才能獲取到“對象的同步鎖”進(jìn)而繼續(xù)運(yùn)行做裙。
總之岗憋,notify(), wait()依賴于“同步鎖”,而“同步鎖”是對象鎖持有锚贱,并且每個(gè)對象有且僅有一個(gè)仔戈!這就是為什么notify(), wait()等函數(shù)定義在Object類,而不是Thread類中的原因拧廊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末监徘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吧碾,更是在濱河造成了極大的恐慌凰盔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倦春,死亡現(xiàn)場離奇詭異户敬,居然都是意外死亡落剪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門尿庐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忠怖,“玉大人,你說我怎么就攤上這事屁倔∧杂郑” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵锐借,是天一觀的道長问麸。 經(jīng)常有香客問我,道長钞翔,這世上最難降的妖魔是什么严卖? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮布轿,結(jié)果婚禮上哮笆,老公的妹妹穿的比我還像新娘。我一直安慰自己汰扭,他們只是感情好稠肘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著萝毛,像睡著了一般项阴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笆包,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天环揽,我揣著相機(jī)與錄音,去河邊找鬼庵佣。 笑死歉胶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巴粪。 我是一名探鬼主播通今,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼验毡!你這毒婦竟也來了衡创?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤晶通,失蹤者是張志新(化名)和其女友劉穎璃氢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狮辽,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡一也,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年巢寡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椰苟。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抑月,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舆蝴,到底是詐尸還是另有隱情谦絮,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布洁仗,位于F島的核電站层皱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赠潦。R本人自食惡果不足惜叫胖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望她奥。 院中可真熱鬧瓮增,春花似錦、人聲如沸哩俭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凡资。三九已至你踩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讳苦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工吩谦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸳谜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓式廷,卻偏偏與公主長得像咐扭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子滑废,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容