Reputation: 2948
The construct below works and does what I want, but I want to understand why it doesn't deadlock.
The example below makes sure that the user has clicked on yes or no on the JOptionPane
box that pops up (on the EDT) prior to carrying on.
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()");
}
}
But it looks like I am entering synchronized(myWaiter)
blocks at the same time with different threads, given the following output:
starting
waiting
invoked
calling notify
notified
done waiting
ending main()
Why doesn't this deadlock?
Upvotes: 4
Views: 179
Reputation: 96434
If the waiting thread went dormant while holding the lock then you would have a deadlock. But that's not how it works, see the API documentation for Object#wait, especially the second paragraph:
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.
When your main thread enters the wait method it releases the lock that it acquired upon entering the synchronized block, so the lock is available for the EDT thread to acquire. The Runnable being executed on the EDT finds the lock available and acquires it, calls notify on it, prints out its message and exits the synchronized block, releasing the lock. At that point the main thread acquires the lock before exiting the wait method, then releases the lock as it exists the block.
Be aware just because wait exits is not evidence that a notification happened, wait can exit without any notification having occurred (this is the spurious wakeup mentioned in the API doc). Also, although in this case your notifying code is waiting on a UI control for input so the main thread is going to wait first, in general you don't want to rely on the wait happening before the notify: if the notification did happen to occur first then the wait could go on indefinitely. You can address both of these issues by having wait be called in a loop (as advised in the documentation above) where you check some condition that is set by the notifying code.
Upvotes: 2
Reputation: 17955
Quoting the documentation for 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.
This explains the lines up to
starting
waiting
invoked <- could have appeared before or after waiting
And quoting the documentation of 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.
This explains
calling notify
notified
Since at this point the EDT thread abandons the synchronized block and releases the lock, the next lines follow:
done waiting
ending main()
Upvotes: 2