Java 同步对象。为什么不会出现这种僵局?

Java synchronization on Object. Why doesn't this deadlock?

下面的构造可以工作并且可以满足我的要求,但我想了解为什么它不会死锁。

下面的示例确保用户在继续之前(在 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()

为什么没有这个死锁?

如果等待线程在持有锁的同时进入休眠状态,那么您将陷入死锁。但这不是它的工作原理,参见 the API documentation for Object#wait,尤其是第二段:

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).

The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait();
     ... // Perform action appropriate to condition
 }

This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.

当您的主线程进入 wait 方法时,它会释放它在进入同步块时获得的锁,因此该锁可供 EDT 线程获取。在 EDT 上执行的 Runnable 找到可用的锁并获取它,调用它的通知,打印出它的消息并退出同步块,释放锁。此时主线程在退出 wait 方法之前获取锁,然后在它存在块时释放锁。

请注意,仅仅因为等待退出并不是通知发生的证据,等待可以在没有任何通知发生的情况下退出(这是 API 文档中提到的虚假唤醒)。此外,虽然在这种情况下您的通知代码正在等待 UI 控件输入,因此主线程将首先等待,但通常您不想依赖通知之前发生的等待:如果通知确实首先发生,然后等待可以无限期地进行。您可以通过在循环中调用 wait 来解决这两个问题(如上面文档中所建议的那样),您可以在循环中检查由通知代码设置的某些条件。

引用 wait

的文档

The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

这解释了直到

的行
starting
waiting
invoked <- could have appeared before or after waiting

并引用 notify

的文档

The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.

这解释了

calling notify
notified

由于此时 EDT 线程放弃同步块并释放锁,接下来的几行如下:

done waiting
ending main()