3

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

4

5 回答 5

3

我弄清楚为什么即使计算器线程根本没有发出 notify() 也会唤醒所有线程:这是因为它完成了 run() 方法的执行。

要查看 notify() 与 notifyAll() 的效果,我必须在调用 notify 后保持计算器运行。

所以这段代码:

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() 调用得到通知,另外两个在计算器完成执行后被唤醒。

将前面代码中的 notify() 替换为 notifyAll() 会得到以下输出:

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
于 2012-11-06T20:01:49.543 回答
3

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

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

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

  1. 我现在可以做点什么吗?
  2. 如果否,请致电wait并转到步骤 1。
  3. 做那件事。

您的notify逻辑必须如下所示:

  1. 使另一个线程可以做某事。
  2. 打电话notify

这些函数不是通用的挂起/恢复机制。它们专门用于在由同步块保护的代码管理的谓词上进行同步。如果需要,您可以使用自己的怀疑/恢复标志或计数来构建暂停/恢复机制。

更新:Måns Rolandi Danielsson 弄清楚了您的具体情况。您的线程正在等待尚未启动/终止的对象。因此,当它向自己发出准备好/完成的信号时,其他线程会看到该信号。

于 2012-11-06T18:54:54.543 回答
0

我在 Exploring Java (Niemeyer) 中找到了关于这种行为的一行:“对于每次调用 notify(),Java 只会唤醒一个在 wait() 调用中处于休眠状态的方法。如果有多个线程在等待,Java 会选择第一个线程遵循先进先出的原则。

探索 Java

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

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

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

于 2012-11-07T09:01:59.207 回答
0

不,因为所有 Reader 对象都被赋予了相同的 Calculator。当 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();
 }
于 2012-11-06T19:07:30.547 回答
0

这种神秘的行为在 Sun 的 JDK 中已经存在好几年了——当一个线程终止时,它会调用notifyAll()自己。

我不知道为什么会这样。但是JDK 7 javadoc现在解释了

http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join%28long%29

join() 使用以 this.isAlive 为条件的 this.wait 调用循环。当线程终止时,将调用 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() 信号;删除它会破坏这些代码。

于 2012-11-11T00:55:42.563 回答