【问题标题】:Unexpected thread wakeup意外的线程唤醒
【发布时间】:2016-03-02 19:03:40
【问题描述】:

我期待下例中的第二个线程挂起,因为它等待一个没有相应通知的对象。相反,它通过了 println,可能是由于虚假唤醒。

public class Spurious {
    public static void main(String[] args) {

        Thread t1 = new Thread() { 
            public void run() { 
                System.out.println("Hey!"); 
            }  
        };
        Thread t2 = new Thread() { 
            public void run() 
            {
                try {
                    synchronized (t1) {
                        t1.wait();
                    }
                } catch (InterruptedException e) {
                    return;
                }
                System.out.println("Done.");
            }
        };
        t1.start();
        t2.start();
    }
}

输出:

Hey!
Done.

另一方面,如果删除“嘿!” println 从第一个线程,第二个线程确实会挂起。这在 MacOS 和 Linux 上都会发生。

知道为什么吗?

【问题讨论】:

  • 只是一个猜测,但线程的死亡可能会导致任何服务员的虚假唤醒。如果没有println(),t1 可能在 t2 开始等待之前就已经死了,并且错过了那个虚假的唤醒。
  • 阅读 Object.wait() 和 Thread.join() 的 javadoc。您不应该对 Thread 类型的对象调用 wait。它在停止时通知,以唤醒加入它的其他线程。
  • @JBNizet 似乎是对的。虽然我无法在 Javadoc 中找到它,但如果您使用像 final Object lock = new Object()t2 这样的锁等待它(而不是等待 t1),那么它总是挂起。
  • 听起来我猜对了。

标签: java multithreading wait notify spurious-wakeup


【解决方案1】:

这不是虚假唤醒,虚假唤醒是由 JVM 中的竞争条件引起的。这是您代码中的竞争条件。

println 使 thread1 保持活动的时间足够长,以便 thread2 可以在 thread1 终止之前开始等待。

一旦 thread1 终止,它就会向所有在其监视器上等待的东西发送通知。 thread2 收到通知并停止等待。

删除 println 减少了 thread1 完成所需的时间,因此 thread1 在 thread2 可以开始等待时已经完成。 thread1 不再活跃,并且在 thread2 开始等待之前它的通知已经发生,因此 thread2 永远等待。

the API for Thread#join 中记录了线程在死亡时发送通知:

此实现使用以 this.isAlive 为条件的 this.wait 调用循环。当线程终止时,将调用 this.notifyAll 方法。建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。

(调用notifyAll的线程必须持有锁,如果其他线程抢到锁,它可以让终止线程保持活动状态并延迟notifyAll,直到终止线程可以获取锁。)

道德(嗯,道德之一)是始终在带有条件变量的循环中等待,请参阅the Oracle tutorial。如果您将 Thread2 更改为如下所示:

    Thread t2 = new Thread() { 
        public void run() 
        {
            try {
                synchronized (t1) {
                    while (t1.isAlive()) {
                        t1.wait();
                    }
                }
            } catch (InterruptedException e) {
                return;
            }
            System.out.println("Done.");
        }
    };

那么无论thread2是否可以在thread1完成之前开始等待,thread2都应该退出。

当然,这是玩具示例的全部领域:

  • 不要扩展 Thread,使用 Runnable 或 Callable。

  • 不要锁定线程。

  • 不要启动线程,使用执行器。

  • 首选更高级别的并发构造来等待/通知。

【讨论】:

  • 感谢您的回答。你说线程“t”的终止隐含地表示所有等待“t”的线程。这在任何地方都有记录吗?我找不到它。
  • @Marco:将其添加到答案中。
  • 有趣:我没想到必须阅读 Thread#join 才能了解有关 Thread#wait 的知识!
【解决方案2】:

您正在等待一个 Thread 对象。这是不好的做法,在 Thread 的 javadoc(确切地说是 Thread.join)中明确不鼓励这样做。

原因是当你调用thread.join() 阻塞直到线程停止运行时,你实际上是在等待线程。当线程停止运行时,它会通知以解除对join() 的所有调用者的阻塞。

由于您在线程上等待,因此当线程停止运行时,您会隐式收到通知。

【讨论】:

  • 这不是正确答案;海报有比赛条件。如果线程已经停止,Thread.Wait() 将无限期阻塞。如果线程正在运行并完成,它将阻塞然后释放。
  • 错了,那又怎样?问题是关于“虚假”唤醒。我的回答解释了这一点。我不明白存在竞争条件这一事实如何使解释不正确。
  • 因为代码中没有虚假唤醒;发帖者对他们的错误的原因感到困惑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多