【问题标题】:Java synchronization on Object. Why doesn't this deadlock?对象上的 Java 同步。为什么不会出现这种僵局?
【发布时间】:2016-05-04 20:31:12
【问题描述】:

下面的构造可以正常工作并且可以执行我想要的操作,但我想了解它为什么不会死锁。

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

为什么不会出现这种死锁?

【问题讨论】:

  • 等待锁释放该锁并在唤醒时重新获取它。

标签: java multithreading deadlock synchronized


【解决方案1】:

引用wait的文档,

线程释放此监视器的所有权并等待直到另一个 thread 通知在这个对象的监视器上等待唤醒的线程 通过调用 notify 方法或 notifyAll 方法。 然后线程等待直到它可以重新获得监视器的所有权 并继续执行。

这解释了直到

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

并引用notify的文档,

被唤醒的线程将无法继续,直到当前 线程放弃对该对象的锁定。被唤醒的线程将 以通常的方式与任何其他线程竞争 积极竞争以同步该对象;例如, 被唤醒的线程在存在时不享有可靠的特权或劣势 锁定此对象的下一个线程。

这就解释了

calling notify
notified

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

done waiting
ending main()

【讨论】:

    【解决方案2】:

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

    使当前线程等待,直到另一个线程为此对象调用 notify() 方法或 notifyAll() 方法。换句话说,此方法的行为与它只是执行调用 wait(0) 完全相同。

    当前线程必须拥有这个对象的监视器。线程释放此监视器的所有权并等待,直到另一个线程通过调用 notify 方法或 notifyAll 方法通知在此对象的监视器上等待的线程唤醒。然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。

    在单参数版本中,中断和虚假唤醒是可能的,并且应该始终在循环中使用此方法:

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

    该方法只能由作为该对象监视器所有者的线程调用。有关线程可以成为监视器所有者的方式的描述,请参见 notify 方法。

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

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

    【讨论】:

    • 感谢您提供详细信息。希望我能接受这两个答案。 :-)
    猜你喜欢
    • 2019-05-26
    • 2020-04-07
    • 1970-01-01
    • 2011-03-31
    • 2016-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多