本章,會對線程等待/喚醒方法進行介紹。涉及到的內容包括:
1. wait(), notify(), notifyAll()等方法介紹
2. wait()和notify()
3. wait(long timeout)和notify()
4. wait() 和 notifyAll()
5. 爲何notify(), wait()等函數定義在Object中,而不是Thread中 html
在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的做用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的做用,則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒全部的線程。java
Object類中關於等待/喚醒的API詳細信息以下:
notify() -- 喚醒在此對象監視器上等待的單個線程。
notifyAll() -- 喚醒在此對象監視器上等待的全部線程。
wait() -- 讓當前線程處於「等待(阻塞)狀態」,「直到其餘線程調用此對象的 notify() 方法或 notifyAll() 方法」,當前線程被喚醒(進入「就緒狀態」)。
wait(long timeout) -- 讓當前線程處於「等待(阻塞)狀態」,「直到其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量」,當前線程被喚醒(進入「就緒狀態」)。
wait(long timeout, int nanos) -- 讓當前線程處於「等待(阻塞)狀態」,「直到其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其餘某個線程中斷當前線程,或者已超過某個實際時間量」,當前線程被喚醒(進入「就緒狀態」)。ide
下面經過示例演示"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()"); // 喚醒當前的wait線程 notify(); } } } public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized (t1) { try { // 啓動「線程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(); } } } }
運行結果:this
main start t1 main wait() t1 call notify() main continue
結果說明:
以下圖,說明了「主線程」和「線程t1」的流程。spa
(01) 注意,圖中"主線程" 表明「主線程main」。"線程t1" 表明WaitTest中啓動的「線程t1」。 而「鎖」 表明「t1這個對象的同步鎖」。
(02) 「主線程」經過 new ThreadA("t1") 新建「線程t1」。隨後經過synchronized(t1)獲取「t1對象的同步鎖」。而後調用t1.start()啓動「線程t1」。
(03) 「主線程」執行t1.wait() 釋放「t1對象的鎖」而且進入「等待(阻塞)狀態」。等待t1對象上的線程經過notify() 或 notifyAll()將其喚醒。
(04) 「線程t1」運行以後,經過synchronized(this)獲取「當前對象的鎖」;接着調用notify()喚醒「當前對象上的等待線程」,也就是喚醒「主線程」。
(05) 「線程t1」運行完畢以後,釋放「當前對象的鎖」。緊接着,「主線程」獲取「t1對象的鎖」,而後接着運行。線程
對於上面的代碼?曾經有個朋友問到過:t1.wait()應該是讓「線程t1」等待;可是,爲何倒是讓「主線程main」等待了呢?
在解答該問題前,咱們先看看jdk文檔中關於wait的一段介紹:code
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method object. In other words, method behaves exactly as it simply performs the call wait(0 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.
中文意思大概是:orm
引發「當前線程」等待,直到另一個線程調用notify()或notifyAll()喚醒該線程。換句話說,這個方法和wait(0)的效果同樣!(補充,對於wait(long millis)方法,當millis爲0時,表示無限等待,直到被notify()或notifyAll()喚醒)。 「當前線程」在調用wait()時,必須擁有該對象的同步鎖。該線程調用wait()以後,會釋放該鎖;而後一直等待直到「其它線程」調用對象的同步鎖的notify()或notifyAll()方法。而後,該線程繼續等待直到它從新獲取「該對象的同步鎖」,而後就能夠接着運行。
注意:jdk的解釋中,說wait()的做用是讓「當前線程」等待,而「當前線程」是指正在cpu上運行的線程!
這也意味着,雖然t1.wait()是經過「線程t1」調用的wait()方法,可是調用t1.wait()的地方是在「主線程main」中。而主線程必須是「當前線程」,也就是運行狀態,才能夠執行t1.wait()。因此,此時的「當前線程」是「主線程main」!所以,t1.wait()是讓「主線程」等待,而不是「線程t1」!htm
wait(long timeout)會讓當前線程處於「等待(阻塞)狀態」,「直到其餘線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量」,當前線程被喚醒(進入「就緒狀態」)。
下面的示例就是演示wait(long timeout)在超時狀況下,線程被喚醒的狀況。
//WaitTimeoutTest.java的源碼 class ThreadA extends Thread { public ThreadA(String name) { super(name); } public void run() { System.out.println(Thread.currentThread().getName() + " run "); // 死循環,不斷運行。 while (true) ; } } public class WaitTimeoutTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized (t1) { try { // 啓動「線程t1」 System.out.println(Thread.currentThread().getName() + " start t1"); t1.start(); // 主線程等待t1經過notify()喚醒 或 notifyAll()喚醒,或超過3000ms延時;而後才被喚醒。 System.out.println(Thread.currentThread().getName() + " call wait "); t1.wait(3000); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
運行結果:
main start t1 main call wait t1 run // 大約3秒以後...輸出「main continue」main continue
結果說明:
以下圖,說明了「主線程」和「線程t1」的流程。
(01) 注意,圖中"主線程" 表明WaitTimeoutTest主線程(即,線程main)。"線程t1" 表明WaitTest中啓動的線程t1。 而「鎖」 表明「t1這個對象的同步鎖」。
(02) 主線程main執行t1.start()啓動「線程t1」。
(03) 主線程main執行t1.wait(3000),此時,主線程進入「阻塞狀態」。須要「用於t1對象鎖的線程經過notify() 或者 notifyAll()將其喚醒」 或者 「超時3000ms以後」,主線程main才進入到「就緒狀態」,而後才能夠運行。
(04) 「線程t1」運行以後,進入了死循環,一直不斷的運行。
(05) 超時3000ms以後,主線程main會進入到「就緒狀態」,而後接着進入「運行狀態」。
經過前面的示例,咱們知道 notify() 能夠喚醒在此對象監視器上等待的單個線程。
下面,咱們經過示例演示notifyAll()的用法;它的做用是喚醒在此對象監視器上等待的全部線程。
public class NotifyAllTest { private static Object obj = new Object(); public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); ThreadA t2 = new ThreadA("t2"); ThreadA t3 = new ThreadA("t3"); t1.start(); t2.start(); t3.start(); try { System.out.println(Thread.currentThread().getName() + " sleep(3000)"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { // 主線程等待喚醒。 System.out.println(Thread.currentThread().getName() + " notifyAll()"); obj.notifyAll(); } } static class ThreadA extends Thread { public ThreadA(String name) { super(name); } public void run() { synchronized (obj) { try { // 打印輸出結果 System.out.println(Thread.currentThread().getName() + " wait"); // 喚醒當前的wait線程 obj.wait(); // 打印輸出結果 System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
運行結果:
t1 wait main sleep(3000) t3 wait t2 wait main notifyAll() t2 continuet3 continuet1 continue
結果說明:
參考下面的流程圖。
(01) 主線程中新建而且啓動了3個線程"t1", "t2"和"t3"。
(02) 主線程經過sleep(3000)休眠3秒。在主線程休眠3秒的過程當中,咱們假設"t1", "t2"和"t3"這3個線程都運行了。以"t1"爲例,當它運行的時候,它會執行obj.wait()等待其它線程經過notify()或額nofityAll()來喚醒它;相同的道理,"t2"和"t3"也會等待其它線程經過nofity()或nofityAll()來喚醒它們。
(03) 主線程休眠3秒以後,接着運行。執行 obj.notifyAll() 喚醒obj上的等待線程,即喚醒"t1", "t2"和"t3"這3個線程。 緊接着,主線程的synchronized(obj)運行完畢以後,主線程釋放「obj鎖」。這樣,"t1", "t2"和"t3"就能夠獲取「obj鎖」而繼續運行了!
Object中的wait(), notify()等函數,和synchronized同樣,會對「對象的同步鎖」進行操做。
wait()會使「當前線程」等待,由於線程進入等待狀態,因此線程應該釋放它鎖持有的「同步鎖」,不然其它線程獲取不到該「同步鎖」而沒法運行!
OK,線程調用wait()以後,會釋放它鎖持有的「同步鎖」;並且,根據前面的介紹,咱們知道:等待線程能夠被notify()或notifyAll()喚醒。如今,請思考一個問題:notify()是依據什麼喚醒等待線程的?或者說,wait()等待線程和notify()之間是經過什麼關聯起來的?答案是:依據「對象的同步鎖」。
負責喚醒等待線程的那個線程(咱們稱爲「喚醒線程」),它只有在獲取「該對象的同步鎖」(這裏的同步鎖必須和等待線程的同步鎖是同一個),而且調用notify()或notifyAll()方法以後,才能喚醒等待線程。雖然,等待線程被喚醒;可是,它不能馬上執行,由於喚醒線程還持有「該對象的同步鎖」。必須等到喚醒線程釋放了「對象的同步鎖」以後,等待線程才能獲取到「對象的同步鎖」進而繼續運行。
總之,notify(), wait()依賴於「同步鎖」,而「同步鎖」是對象鎖持有,而且每一個對象有且僅有一個!這就是爲何notify(), wait()等函數定義在Object類,而不是Thread類中的緣由。