【问题标题】:java.util.concurrent.locks.Condition awaitUninterruptibly()java.util.concurrent.locks.Condition awaitUninterruptibly()
【发布时间】:2015-01-14 07:53:39
【问题描述】:

当我阅读java.util.concurrent.locks.Condition API 文档时,

我明白了:

在等待条件时,允许“虚假唤醒” 通常,作为对底层平台的让步 语义。这对大多数应用程序几乎没有实际影响 作为条件的程序应始终在循环中等待, 测试正在等待的状态谓词。一个 实现是免费的,以消除虚假唤醒的可能性 但建议应用程序程序员始终假设 它们可能会发生,因此总是在循环中等待

awaitUninterruptibly() 说:

如果当前线程进入此状态时设置了中断状态 方法,否则在等待中被打断,会继续等待 直到发出信号。当它最终从此方法返回时 仍将设置中断状态

那么,这是否意味着 我们不需要在循环中调用 awaitUninterruptibly() ?请澄清。提前致谢。

【问题讨论】:

  • 中断不是虚假唤醒,它们仍然可能发生。
  • @PeterLawrey 调用 awaitUninterruptibly() 的线程会继续等待直到发出信号,而不是处于循环中?
  • 仅当它没有虚假唤醒时。
  • @PeterLawrey 感谢您的揭秘。现在明白了,建议我们在循环中调用任何wait()s 来处理虚假唤醒。
  • 总是在循环中等待()。在某些应用程序中——甚至可能——当一个线程重新获得锁并从 wait() 调用返回时,它正在等待的条件已经因为某些人的活动而再次变为 false其他线程。即使在您今天编写的应用程序中不会发生这种情况,循环也不会损害性能,并且始终使用循环的习惯可能会防止您在 next 中犯错误应用程序。

标签: java multithreading concurrency wait


【解决方案1】:

specification is pretty clear:

void awaitUninterruptibly()

使当前线程等待,直到它发出信号。 与此条件关联的锁会自动释放,并且当前线程出于线程调度目的而被禁用并处于休眠状态,直到发生以下三种情况中的 一种

  • 其他某个线程为此Condition调用了signal()方法,而当前线程恰好被选为要被唤醒的线程;或
  • 其他一些线程为此条件调用signalAll() 方法;或
  • 发生“虚假唤醒”。

因此,中断不在可能的唤醒条件列表中,但 虚假唤醒 是。不要让你通过查看特定的实现代码来指导。您的应用程序最终运行的实现可能完全不同。

此外,Condition 是一个 interface,即使在一个运行时环境中也可能有不同的实现。 This answer 甚至没有指定这段代码来自哪个具体类。

您必须使用带有awaitUninterruptibly() 的常用循环来执行等待操作。

考虑以下几点:

  • 信号和等待之间没有 1:1 的映射,
    • 因此您可能会错过线程唤醒之前发生的一个或多个信号
    • 更糟糕的是,信号不会被记住,因此在另一个线程开始等待之前发生的信号会丢失
  • 因此您必须在决定等待之前预先检查所需的条件状态
  • 这意味着信号可能会唤醒一个线程,但另一个线程会由于成功的预检查而消耗状态,因此唤醒的线程必须重新检查所需的条件状态

因此,即使没有虚假唤醒,也需要在等待前进行预检查并在唤醒后进行重新检查的循环。

【讨论】:

    【解决方案2】:

    来自代码:

    public final void awaitUninterruptibly() {
      Node node = addConditionWaiter();
      int savedState = fullyRelease(node);
      boolean interrupted = false;
      while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if (Thread.interrupted()) interrupted = true;
      }
      if (acquireQueued(node, savedState) || interrupted) selfInterrupt();
    }
    

    所以等待是在一个循环中完成的,这将消除在这个函数之外循环的需要。

    但是请记住,这也意味着 Thread.interrupt() 不会做任何事情,这可能会导致您的代码发生某些锁定,即关机期间。

    【讨论】:

    • 方法内部的循环与应用程序必须执行的循环完全不同。应用程序的循环必须检查此方法甚至不知道的特定于应用程序的条件。除此之外,您正在将特定的实现代码与 contract. 混合在一起