我希望以下示例中的第二个线程挂起,因为它等待没有相应通知的对象。相反,它会落到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上。
知道为什么吗?
答案 0 :(得分:5)
这不是一个虚假的唤醒,一个虚假的唤醒是由JVM中的竞争条件引起的。这是您代码中的竞争条件。
println使thread1保持忙碌的时间足够长,以至于thread2可以在thread1终止之前开始等待。一旦thread1终止,它就会向其监视器上等待的所有内容发送通知。 thread2收到通知并停止等待。
删除println会减少thread1显着完成所需的时间,以便在thread2可以开始等待它时,thread1已经完成。在thread2开始等待之前,thread1不再处于活动状态并且已经发出通知,因此thread2将永远等待。
线程在死亡时发送通知记录在the API for Thread#join:
中此实现使用this.wait调用on.isAlive的循环。当一个线程终止时,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait,notify或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应该退出,无论thread2是否可以在thread1完成之前开始等待。
当然,这是一个完整的玩具示例区域:
不要扩展线程。
不要锁定线程。
不要启动线程,使用执行程序。
更喜欢更高级别的并发结构来等待/通知。
答案 1 :(得分:1)
您正在等待Thread对象。这是一个不好的做法,在Thread的线程(Thread.join)中明确不鼓励。
原因是当你调用thread.join()
阻塞直到线程停止运行时,你实际上正在等待线程。当线程停止运行时,它会通知以取消阻止join()
的所有呼叫者。
由于您在线程上等待,当线程停止运行时,您将被隐式通知。