簡體   English   中英

在單個線程上發出通知會喚醒所有等待的線程

[英]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邏輯必須如下所示:

  1. 我現在可以做點什么嗎?
  2. 如果不是,請wait並轉到步驟1。
  3. 做那件事。

您的notify邏輯必須如下所示:

  1. 使它成為另一個線程可以做某事。
  2. 致電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會選擇第一個以先進先出為基礎的線程。

探索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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM