【问题标题】:if java wait() is called on a Thread, the method exits also on run() method termination如果在线程上调用 java wait(),则该方法也会在 run() 方法终止时退出
【发布时间】:2013-05-29 01:27:42
【问题描述】:

我对 wait() 方法的一个特定用例感到困惑。
根据 javadoc,当以下情况之一发生时,等待应该结束:

  • 另一个线程调用 notify 或 notifyAll(好的,有关 notify 的详细信息,请参见 javadoc,但这与此问题无关)
  • 另一个线程中断了这个(等待的)线程
  • 超时过期(如果使用带超时的等待版本)

如果我正在等待的对象本身就是一个线程,那么即使没有调用 notify(),并且上述条件都不成立,wait() 也会退出。 但它发生在 Thread.run() 方法结束时。 虽然这种行为可能有意义,但它不应该记录在 Thread javadoc 中吗? 我也觉得它很混乱,因为它与 join() 行为重叠。

这是我的测试代码:

public static class WorkerThread extends Thread {

    @Override public void run() {

        try{
           System.out.println("WT: waiting 4 seconds");
           Thread.sleep(4000);

           synchronized (this) {
            notify();
           }

           System.out.println("WT: waiting for 4 seconds again");
           Thread.sleep(4000);
           System.out.println("WT: exiting");

        } catch (InterruptedException ignore) {
            ignore.printStackTrace();
        }

    }

}

public static void main (String [] args) throws InterruptedException {

    WorkerThread w = new WorkerThread();

    w.start();

    synchronized(w) {
        w.wait();
        System.out.println("MT: The object has been notified by the thread!");
    }

    synchronized(w) {

        w.wait(); //THIS FINISHES WITHOUT notify(), interrupt() OR TIMEOUT!

        System.out.println("MT: The thread has been notified again!");
    }

}

【问题讨论】:

  • 你怎么知道notify没有被调用?你假设因为你没有调用notifyAll,所以它不能被调用。但这是一个完全虚假的假设。
  • 因为如果它被调用,它应该被记录在案,因为这将是类合同的一个非常重要的部分。如果我不能依赖实现者(在这种情况下是一个自动的)声明在一个非常具体的情况下它将调用 notify() 的事实,那么我将永远不会信任除我创建的类之外的任何类。
  • 对于一个您无法控制其实现的对象,您不能依赖等待/通知的语义,因为实现可能需要等待某事并因为某事而通知。
  • 显然,Sun 使用 synchronized(thread) 来实现 thread.join() 是一个错误。我亲眼见过很多让人们感到困惑的案例。 Sun/Oracle 一定收到了很多关于它的抱怨,所以他们终于把它记下来了。但是 thread.join() 并不是写那个笔记的好地方。

标签: java multithreading


【解决方案1】:

它自 Java 7 起被记录在 documentation of the join() method:

当线程终止时,将调用 this.notifyAll 方法。建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。

【讨论】:

  • 所以看起来他们已经修复了文档中的疏忽,但实际上它也应该在 java 1.6 中记录。我查了,没有写,行为也是一样的。
  • @AgostinoX Even in prior versions, the javadoc for wait says: "线程也可以在没有被通知、中断或超时的情况下唤醒,即所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范它,如果条件不满足则继续等待。"。所以发生的事情其实是符合wait的规范的。
【解决方案2】:

直接扩展Thread是一种常见的反模式。问题是您可能会产生各种意想不到的后果,因为 Thread 是一个复杂的类。它所做的一件事是在线程停止时通知线程尝试加入()线程。这具有通知任何在该线程对象上等待的线程的效果。

一个常见的 Java 难题是错过使用扩展 Thread 的类。

顺便说一句,虽然使用 wait()/notify() 已经有 9 年多的历史了,但您仍然可以在极少数情况下使用它们。如果你这样做了,你应该在 notify() 块中改变某些东西的状态,并在你 wait() 时等待状态改变。这是因为如果你在等待之前调用 notify,它会丢失并且 wait() 可能会虚假存在,即使你没有扩展线程。

【讨论】:

  • 所有官方文档都提出了扩展 Thread 是一种可行的方式。我会考虑你的建议,但我不知道这是否真的是一种反模式,还是更有用的预防措施。当您谈论“老派”时,这很有趣。新学校的通知首选是什么?
  • @AgostinoX 你应该更喜欢implementing Runnable vs. extending Thread。新学校=java.util.concurrent。特别是,它包含了几种实现通知的方法,例如CountDownLatch、CyclicBarrier、Exchanger 等 + 当你想跨线程交换消息时所有的阻塞队列。而且它有几个 ExecutorService 的实现,所以在很多情况下你真的不需要手动启动线程。
【解决方案3】:

如果您想要可预测和可控的等待/通知行为,请在线程和等待它的方法之间共享一个对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-13
    • 2013-12-18
    • 1970-01-01
    • 1970-01-01
    • 2011-03-03
    相关资源
    最近更新 更多