【问题标题】:Is it okay to call notify() on a lock object before calling wait() on it?在调用 wait() 之前调用 lock 对象上的 notify() 是否可以?
【发布时间】:2016-11-04 23:04:00
【问题描述】:

我在我的代码中使用了 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) {
          }                                              
    }

【问题讨论】:

  • 如果一棵树倒在森林里,但没有人听到,它会发出声音吗?
  • 我也是这么想的。想确认,不想假设。

标签: java multithreading wait notify


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    是否可以在调用 wait() 之前对 lock 对象调用 notify()?

    嗯,这取决于上下文。从表面上看,这并没有什么坏处。

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

    但是,您的代码因其他原因而损坏。

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

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

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

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

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

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

    这就是你应该实现它的方式。如您所见,代码比您的解决方案少,而且更容易理解:

    线程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.
          }                                              
      }
    

    【讨论】:

      【解决方案3】:

      不知何故,您必须知道是否有要等待的东西。否则,您将无法知道是否调用wait。您正在等待的这个东西称为“谓词”。在您的情况下,isLoadComplete 已经是谓词,因此使用另一个标志来跟踪您是否需要等待是多余的。

      当且仅当isLoadComplete 是false 时,您才需要等待。当且仅当您将isLoadComplete 设置为true 时,您才调用notify。是什么让您认为您需要更多?

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

      【讨论】:

        【解决方案4】:

        当你发现自己在使用

        wait()

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

        循环应该是这样的

        while (_whatever_im_waiting_for_to_happen_has_not_happened_)
            wait();
        }
        

        【讨论】:

          猜你喜欢
          • 2012-05-10
          • 1970-01-01
          • 1970-01-01
          • 2013-04-18
          • 1970-01-01
          • 1970-01-01
          • 2013-11-01
          • 2012-12-19
          相关资源
          最近更新 更多