在锁定对象上调用 wait() 之前调用 notify() 可以吗?

Is it okay to call notify() on a lock object before calling wait() on it?

我在我的代码中使用了 wait() 和 notify() 并且有一种情况,在另一个线程的锁对象上实际调用等待之前,执行可以先到达 notify() 。我在锁定对象中创建了一个标志 "isWaitCalled" 并在尚未调用 wait() 时使用它来跳过 notify() 调用。这个标志 "isWaitCalled" 是多余的吗?如果在其中一种情况下等待之前调用 notify() 可以吗? 线程 A:

synchronized (syncObject) {
          try {
                if (!syncObject.isLoadComplete) {
                    syncObject.isWaitCalled = true;
                    syncObject.wait();
                }
          } catch (Exception ex) {
          }                                              
    }

线程B:

synchronized (syncObject) {
          try {
                if (!syncObject.isLoadComplete) {
                  syncObject.isLoadComplete =true;
                   if (syncObject.isWaitCalled) {
                       syncObject.notify();         
                   }                  
               }
          } catch (Exception ex) {
          }                                              
    }

总之,有没有等,总得知道吧。否则,你无法知道是否调用wait。您正在等待的这个东西叫做 "predicate"。在您的情况下,isLoadComplete 已经是谓词,因此使用另一个标志来跟踪您是否需要等待是多余的。

当且仅当 isLoadCompletefalse 时,您才需要等待。当且仅当您将 isLoadComplete 设置为 true 时,您才调用 notify。是什么让您认为自己需要的不止于此?

在没有线程等待时调用 notify 是无害的。

在没有线程等待的对象上调用 notify()notifyAll() 没有特殊问题。特别是,它不会阻塞,从这个意义上说,isWaitCalled 变量没有任何作用。

但是请注意,通知仅对在传递通知时已经在等待的线程有效。特别是,在 notify() 之后调用对象的 wait() 方法的线程将阻塞,直到 next 通知。出于这个原因,标准的等待/通知范式要求线程在等待之前检查它们是否需要等待。

在最简单的形式中,线程将检查一些共享变量的值以确定是否等待以及从等待返回后是否继续等待。就通知线程而言,它负责在发送通知之前适当地修改该变量。这或多或少与您介绍的相反。

Is it okay to call notify() on a lock object before calling wait() on it?

好吧,这取决于上下文。从表面上看,它没有坏处。

但是,如果您的代码依赖于调用 wait() 的线程查看特定通知,那么它将无法工作。通知没有排队。如果在 notify() 调用发生时没有等待,则丢弃通知。

但是,由于其他原因,您的代码已损坏。

  1. Java 等待/通知会产生虚假通知;即 wait() 中的线程可能会在没有特定 notify()notifyAll() 的情况下被唤醒。您的代码假定 loadComplete 已在线程 A 中的 wait() 的 return 之后设置。它不能假设...因为虚假唤醒。

  2. 您假设只有一个线程可能正在等待加载完成。如果不是这样,那么一个线程将被唤醒,其余线程可能会永远卡住。

  3. 最后,像这样捕获和丢弃 Exception 在两个方面是错误的。

    • 您正在捕获/压缩任何其他已检查或未检查的异常(Error 等除外)。
    • 您忽略了一个您应该处理的已检查异常。如果线程 A 或线程 B 在不幸的时间获得中断,那么事情就会中断。特别是线程 A 可能认为代码已经加载,但实际上并没有。 如果您不打算处理中断,则将它们视为 "this should never happen" 案例...并抛出 AssertionError 或同样致命的东西。

我建议您 使用标准模式来实现 java.lang.Object javadoc 中提供的条件变量。使用循环,不要尝试优化不必要通知的情况。如果您有优化的冲动,那么请尝试使用一种更高级别的并发结构(例如 "latch"),而不是实施您自己的解决方案。

如果你得到 "creative",你可能会遇到麻烦。即使你的代码是完美的,你也会给维护你代码的人带来额外的工作。如果他们遇到神秘的并发错误,他们可能需要从第一原则(重新)分析您的 "creative" 解决方案以确定它是否真的合理。

这就是您应该实现的方式。可以看到,比你的方案代码少,也更容易理解:

线程A:

  synchronized (syncObject) {
      try {
          // The loop deals with spurious wakeups.
          while (!syncObject.isLoadComplete) {
              syncObject.wait();
          }
      } catch (InterruptedException ex) {
           // This is the "easy" way to do it correctly, in the case
           // where interrupts are not designed for.
           throw AssertionError("interrupted unexpectedly");
      }                                              
  }

线程B:

  synchronized (syncObject) {
      if (!syncObject.isLoadComplete) {
          syncObject.isLoadComplete = true;
          syncObject.notifyAll();  // use `notify()` only if threadA 
                                   // is guaranteed to be the only waiter.
      }                                              
  }

当您发现自己在使用

wait()

方法,并且该方法调用不在循环中,您几乎可以肯定做错了什么。

循环的形式应该是

while (_whatever_im_waiting_for_to_happen_has_not_happened_)
    wait();
}