本章,會對線程等待/喚醒方法進行介紹。涉及到的內容包括:
1. wait(), notify(), notifyAll()等方法介紹
2. wait()和notify()
3. wait(long timeout)和notify()
4. wait() 和 notifyAll()
5. 為什么notify(), wait()等函數定義在Object中,而不是Thread中
wait(), notify(), notifyAll()等方法介紹
在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。
Object類中關于等待/喚醒的API詳細信息如下:
notify() -- 喚醒在此對象監視器上等待的單個線程。
notifyAll() -- 喚醒在此對象監視器上等待的所有線程。
wait() -- 讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout) -- 讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) -- 讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。
2. wait()和notify()示例
下面通過示例演示"wait()和notify()配合使用的情形"。
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();
}
}
}
}
運行結果:
(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的一段介紹:
中文意思大概是:
引起“當前線程”等待,直到另外一個線程調用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”!
3. wait(long timeout)和notify()
wait(long timeout)會讓當前線程處于“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
下面的示例就是演示wait(long timeout)在超時情況下,線程被喚醒的情況。
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();
}
}
}
}
運行結果:
結果說明:
如下圖,說明了“主線程”和“線程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會進入到“就緒狀態”,然后接著進入“運行狀態”。
4. wait() 和 notifyAll()
通過前面的示例,我們知道 notify() 可以喚醒在此對象監視器上等待的單個線程。
下面,我們通過示例演示notifyAll()的用法;它的作用是喚醒在此對象監視器上等待的所有線程。
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();
}
}
}
}
}
運行結果:
結果說明:
參考下面的流程圖。
(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鎖”而繼續運行了!
5. 為什么notify(), wait()等函數定義在Object中,而不是Thread中
Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。
wait()會使“當前線程”等待,因為線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!
OK,線程調用wait()之后,會釋放它鎖持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒?,F在,請思考一個問題:notify()是依據什么喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什么關聯起來的?答案是:依據“對象的同步鎖”。
負責喚醒等待線程的那個線程(我們稱為“喚醒線程”),它只有在獲取“該對象的同步鎖”(這里的同步鎖必須和等待線程的同步鎖是同一個),并且調用notify()或notifyAll()方法之后,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因為喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之后,等待線程才能獲取到“對象的同步鎖”進而繼續運行。
總之,notify(), wait()依賴于“同步鎖”,而“同步鎖”是對象鎖持有,并且每個對象有且僅有一個!這就是為什么notify(), wait()等函數定義在Object類,而不是Thread類中的原因。
新聞熱點
疑難解答