下面的结构可以正常工作并完成我想要的工作,但我想了解它为什么不会死锁。
以下示例确保用户在继续之前弹出(在EDT上)的JOptionPane
框上点击了是或否。
package waitexample;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class WaitExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("starting");
Object myWaiter = new Object();
SwingUtilities.invokeLater(() -> {
System.out.println("invoked");
JOptionPane.showConfirmDialog(null, "Message", "Title", JOptionPane.YES_NO_OPTION);
synchronized (myWaiter) {
System.out.println("calling notify");
myWaiter.notify();
System.out.println("notified");
}
});
synchronized (myWaiter) {
System.out.println("waiting");
myWaiter.wait();
System.out.println("done waiting");
}
System.out.println("ending main()");
}
}
但看起来我使用不同的线程同时输入synchronized(myWaiter)
块,给出以下输出:
starting
waiting
invoked
calling notify
notified
done waiting
ending main()
为什么不会出现这种僵局?
答案 0 :(得分:2)
如果等待线程在持有锁的情况下处于休眠状态,则会出现死锁。但这不是它的工作方式,请参阅the API documentation for Object#wait,尤其是第二段:
使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法。换句话说,此方法的行为就像它只是执行call wait(0)一样。
当前线程必须拥有此对象的监视器。该线程释放该监视器的所有权并等待,直到另一个线程通过调用notify方法或notifyAll方法通知等待该对象监视器的线程唤醒。然后该线程等待,直到它可以重新获得监视器的所有权并继续执行。
在一个参数版本中,中断和虚假唤醒是可能的,并且此方法应始终在循环中使用:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
此方法只能由作为此对象监视器所有者的线程调用。有关线程可以成为监视器所有者的方式的说明,请参阅notify方法。
当主线程进入wait方法时,它会释放它在进入同步块时获取的锁定,因此锁定可供EDT线程获取。在EDT上执行的Runnable发现锁可用并获取它,在其上调用notify,打印出它的消息并退出synchronized块,释放锁。此时主线程在退出wait方法之前获取锁,然后释放锁,因为它存在于块中。
请注意,因为等待退出不是发生通知的证据,等待可以在没有任何通知发生的情况下退出(这是API文档中提到的虚假唤醒)。此外,虽然在这种情况下,您的通知代码正在等待UI控件的输入,因此主线程将首先等待,通常您不希望依赖于通知之前发生的等待:如果通知已执行碰巧先发生,然后等待可以无限期地继续下去。您可以通过在循环中调用等待来解决这两个问题(如上面的文档中所建议的那样),您可以在其中检查通知代码设置的某些条件。
答案 1 :(得分:2)
引用wait,
的文档线程释放此监视器的所有权并等到另一个监视器 线程通知线程等待此对象的监视器唤醒 通过调用notify方法或notifyAll方法。 然后线程等待,直到它可以重新获得监视器的所有权 并恢复执行。
这解释了
之前的界限starting
waiting
invoked <- could have appeared before or after waiting
并引用notify的文档,
唤醒的线程将无法继续直到当前 线程放弃对此对象的锁定。被唤醒的线程会 以通常的方式与任何其他可能的线程竞争 积极竞争同步这个对象;例如, 被唤醒的线程没有可靠的特权或劣势 下一个锁定此对象的线程。
这解释了
calling notify
notified
此时EDT线程放弃同步块并释放锁定,接下来的行如下:
done waiting
ending main()