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

Posted

技术标签:

【中文标题】在单个线程上发出通知会唤醒所有等待的线程【英文标题】:Issuing notify on a single thread awakens all waiting threads 【发布时间】:2012-11-06 18:46:40 【问题描述】:

有 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);指令吗?

【问题讨论】:

【参考方案1】:

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

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

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

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

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

    让另一个线程可以做某事。 致电notify

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

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

【讨论】:

实际上,在阅读了您的 cmets 之后,我评论了 notify() 指令,但等待的线程仍然自行唤醒。所以如果我明白你在说什么:即使我没有明确地为它设置超时并且没有发出通知,线程也可以自行唤醒? 您可能还有其他事情要做。虽然我的回答是正确的,并且您正在做的事情不应该起作用,但它通常“碰巧”起作用。所以也许你的线程被打断或者你有其他问题。但是,您仍然应该学习如何正确使用通知/等待。 (您的问题的答案是肯定的,明确允许虚假唤醒。) 我建议使用更高级别的抽象来做到这一点,例如信号量。 大卫,这不是生产代码,我正在尝试等待/通知的概念。如果我说 - 基于上面的代码和你的回答 - 等待并不能保证等待线程不会自行唤醒,这是否正确?谢谢 没错。您必须使用wait 来等待某事。您不能将其用作挂起机制。 (例如:while (wakecount == 0) wait();wakecount++; notify();【参考方案2】:

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

【讨论】:

我明白你在说什么,有趣!任何人都可以解释这种行为吗?【参考方案3】:

不,因为所有 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();
 

【讨论】:

你是对的!这就是对他特定结果的解释。 那么 -1 是从哪里来的?? 一个不知道他们认为自己知道什么的人,我想。 不,他错了。即使没有 notify 或 notifyAll 等待的线程也会被唤醒。此外,计算器不会通知“自身”,它会选择一个等待该特定实例的线程来通知它,而 notifyAll 会通知该实例上的所有等待线程(请参阅文档)。 不,他是对的。等待线程被唤醒,因为它们正在等待准备好/完成的对象。计算器确实会通知自己,因为这就是 Java 线程的工作方式。 (当你启动一个对象时,它就准备好了,并通知自己,这样它就可以开始工作了。)See here.【参考方案4】:

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

Exploring Java

所以,对计算器使用这个 run() 方法:

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于在单个线程上发出通知会唤醒所有等待的线程的主要内容,如果未能解决你的问题,请参考以下文章

内置锁synchronized下的等待通知机制

线程通信

线程通讯

通知后跟另一个通知[重复]

如何等待线程通知而不是加入所有线程?

notify()notifyAll()wait()方法