4

我期待以下示例中的第二个线程挂起,因为它等待一个没有相应通知的对象。相反,它通过了 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 上。

知道为什么吗?

4

2 回答 2

6

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

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

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

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

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

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

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

道德(嗯,道德之一)是始终在带有条件变量的循环中等待,请参阅Oracle 教程。如果将 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 应该退出,无论 thread2 是否可以在 thread1 完成之前开始等待。

当然,这是整个玩具示例领域:

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

  • 不要锁定线程。

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

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

于 2016-03-02T19:31:02.540 回答
2

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

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

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

于 2016-03-02T19:18:53.703 回答