[英]Java multithreading: how to notify() a single thread after all threads are ready
[英]Issuing notify on a single thread awakens all waiting threads
有三個線程在第4個線程上等待,后者發出通知,所有等待的線程都被喚醒。
這是源代碼:
class Reader extends Thread {
Calculator calc;
public Reader(Calculator calc) {
this.calc = calc;
}
public void run() {
synchronized(calc) {
try {
System.out.println(Thread.currentThread().getName() + " waiting for calc");
calc.wait();
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + " Total is: " + calc.total);
}
}
}
class Calculator extends Thread {
public int total = 0;
public void run() {
synchronized(this) {
for (int i = 0; i < 50; i++) {
total += i;
}
notify();
System.out.println("I notified a thread");
}
}
}
public class Notify {
public static void main(String[] args) {
Calculator calc = new Calculator();
Reader r1 = new Reader(calc);
Reader r2 = new Reader(calc);
Reader r3 = new Reader(calc);
r1.start();
r2.start();
r3.start();
calc.start();
}
}
這是我得到的輸出:
Thread-2 waiting for calc
Thread-4 waiting for calc
Thread-3 waiting for calc
I notified a thread
Thread-2 Total is: 1225
Thread-3 Total is: 1225
Thread-4 Total is: 1225
不應該只喚醒一個等待線程並執行System.out.println(Thread.currentThread().getName() + " Total is: " + calc.total);
指導?
您不能以這種方式使用wait
/ notify
。 你必須wait
等待某事 ,你的代碼可以,而且必須測試。 您應該只在更改另一個線程實際等待的內容后調用notify
,以便其測試將告訴它不再等待它。
“線程也可以在沒有被通知,中斷或超時的情況下喚醒,即所謂的虛假喚醒。雖然這在實踐中很少發生,但是應用程序必須通過測試應該導致該線程的條件來防范它覺醒,如果條件不滿意,繼續等待。“
換句話說,您的wait
邏輯必須如下所示:
wait
並轉到步驟1。 您的notify
邏輯必須如下所示:
notify
。 這些函數不是通用的掛起/恢復機制。 它們特別是一種在由synchronized塊保護的代碼管理的謂詞上進行同步的方法。 如果你想使用你自己的嫌疑/恢復標志,你可以建立一個暫停/恢復機制,如果你願意,可以計數。
更新:MånsRolandiDanielsson想出了具體案例中發生的事情。 您的線程正在等待尚未啟動/終止的對象。 因此,當它發出信號表示已准備好/完成時,其他線程會看到該信號。
我想出了為什么所有線程都被喚醒,即使計算器線程根本沒有發出notify():這是因為它完成了run()方法的執行。
要查看notify()與notifyAll()的效果,我必須在通知調用后保持計算器運行。
所以這段代碼:
class Calculator extends Thread {
int total;
public void run() {
synchronized(this) {
for (int i = 0; i < 50; i++) {
total += i;
}
notify();
System.out.println("I notified a thread");
}
try {
System.out.println("Going to sleep");
Thread.sleep(5000);
System.out.println("I finished sleeping");
} catch(InterruptedException e) {}
}
}
給出以下輸出:
Thread-2 waiting for calc
Thread-4 waiting for calc
Thread-3 waiting for calc
I notified a thread
Thread-2 Total is: 1225
Going to sleep
I finished sleeping
Thread-3 Total is: 1225
Thread-4 Total is: 1225
這表明只有一個等待線程通過notify()調用得到通知,其他兩個在計算器完成執行后被喚醒。
在前面的代碼中用notifyAll()替換notify()會得到以下輸出:
Thread-2 waiting for calc
Thread-4 waiting for calc
Thread-3 waiting for calc
I notified all threads
Thread-3 Total is: 1225
Thread-4 Total is: 1225
Thread-2 Total is: 1225
Going to sleep
I finished sleeping
不,因為所有Reader對象都被賦予相同的計算器。 當Reader線程啟動時,在同一個Calculator對象上調用calc.wait(),然后當Calculator啟動並通知自身時,所有Reader線程立即退出。 瞧! 編輯嘗試改為:
public static void main(String[] args) throws InterruptedException,
IOException {
Calculator calc1 = new Calculator();
Calculator calc2 = new Calculator();
Calculator calc3 = new Calculator();
Reader r1 = new Reader(calc1);
Reader r2 = new Reader(calc2);
Reader r3 = new Reader(calc3);
r1.start();
r2.start();
r3.start();
calc2.start();
}
EDIT2如果您嘗試下面的測試代碼,您可以看到共享Calculator對象的r1和r2會被通知並退出,而擁有自己的Calculator的r3會一直等待:
public static void main(String[] args) throws InterruptedException,
IOException {
Calculator calc1 = new Calculator();
Calculator calc2 = new Calculator();
Reader r1 = new Reader(calc1);
Reader r2 = new Reader(calc1);
Reader r3 = new Reader(calc2);
r1.start();
r2.start();
r3.start();
calc1.start();
}
我在Exploring Java(Niemeyer)中找到了關於這種行為的一行:“對於每次調用notify(),Java只喚醒一個在wait()調用中睡着的方法。 如果有多個線程在等待,那么Java會選擇第一個以先進先出為基礎的線程。 “
因此,對Calculator使用此run()方法:
public void run() {
int k = 0;
while (k++ < 2) {
synchronized (this) {
notify();
}
}
}
將按照他們在計算器實例上調用wait()方法的順序一次一個地通知前兩個等待實例,並在退出時調用notifyAll,這將通知任何剩余的讀取器等待此特定實例。 我相信,這是對我們在這個非常有趣的討論中所看到的行為的完整解釋。
這種神秘的行為已經存在於Sun的JDK中多年 - 當一個線程終止時,它會調用notifyAll()
。
我不知道為什么會這樣做; 但JDK 7 javadoc現在解釋道
http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join%28long%29
join()使用this.wait調用的循環以this.isAlive為條件。 當一個線程終止時,將調用this.notifyAll方法。 建議應用程序不要在Thread實例上使用wait,notify或notifyAll。
所以基本上,join()實現為
void join()
synchronized(this) // this thread object
while( isAlive() )
wait(); // depends on a notify when this thread terminates
所以JDK為了自己的目的而這樣做。
原因很簡單 - 使用另一個鎖來實現join()會更好。
但JDK現在可能無法改變行為; 可能有一些代碼在不知不覺中/錯誤地依賴於此notifyAll()信號; 刪除它會破壞這些代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.