Object上的Java同步。为什么不会出现这种僵局?

时间:2016-01-27 18:14:37

标签: java multithreading deadlock synchronized

下面的结构可以正常工作并完成我想要的工作,但我想了解它为什么不会死锁。

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

为什么不会出现这种僵局?

2 个答案:

答案 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()