在单个线程上发出通知会唤醒所有等待的线程

时间:2012-11-06 18:46:41

标签: java multithreading concurrency

有三个线程在第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);指令?

5 个答案:

答案 0 :(得分:3)

您不能以这种方式使用wait / notify。您必须使用wait等待获取某些内容,您的代码可以并且必须进行测试。你应该只在更改了另一个线程实际等待的内容之后调用notify,这样它的测试就会告诉它不再等待它。

  

“线程也可以在没有被通知,中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但是应用程序必须通过测试应该导致的条件来防范它要被唤醒的线程,如果条件不满意则继续等待。“

换句话说,您的wait逻辑必须如下所示:

  1. 我现在能做点什么吗?
  2. 如果不是,请致电wait并转到第1步。
  3. 做那件事。
  4. 您的notify逻辑必须如下所示:

    1. 使其成为另一个线程可以做某事。
    2. 致电notify
    3. 这些功能不是通用的暂停/恢复机制。它们特别是一种在由synchronized块保护的代码管理的谓词上进行同步的方法。如果你想使用你自己的嫌疑/恢复标志,你可以建立一个暂停/恢复机制,如果你愿意,可以计算。

      更新:MånsRolandiDanielsson想出了具体案例中发生的事情。您的线程正在等待尚未启动/终止的对象。因此,当它发出信号表示已准备好/已完成时,其他线程会看到该信号。

答案 1 :(得分:3)

我弄清楚为什么所有线程都被唤醒,即使计算器线程根本没有发出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

答案 2 :(得分:0)

不,因为所有Reader对象都被赋予相同的计算器。当Reader线程启动时,calc.wait()在同一个Calculator对象上调用 ,然后当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();
 }

答案 3 :(得分:0)

我在Exploring Java(Niemeyer)中找到了关于此行为的一行: “对于每次调用notify(),Java只唤醒一个在wait()调用中睡着的方法。如果有多个线程在等待,那么Java会以先进先出的方式选择第一个线程

Exploring Java

因此,对计算器使用此run()方法:

public void run() {
    int k = 0;
    while (k++ < 2) {
        synchronized (this) {
            notify();
        }
    }
}

将按照他们在计算器实例上调用wait()方法的顺序一次一个地通知前两个等待实例,并在退出时调用notifyAll,这将通知任何剩余的读取器等待此特定实例。我相信,这是对我们在这次非常有趣的讨论中所看到的行为的完整解释。

答案 4 :(得分:0)

这种神秘的行为已经在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()信号;删除它会破坏这些代码。