【问题标题】:How to wake up all threads waiting on same condition?如何唤醒所有等待相同条件的线程?
【发布时间】:2013-10-02 11:49:32
【问题描述】:

我有以下情况。几个线程在相同的条件下等待。并且当被通知时,都应该停止等待,更改标志并返回对象:

 public Object getObject(){
    lock.lock();
    try {
        while (check)){
            condition.await();
        }

        return returnObjectAndSetCheckToFalse();
    } finally {
        lock.unlock();
    }
}

但是这段代码不起作用,因为较快的线程可能会将检查标志更改为 false,而第二个较慢的线程将再次阻塞。 可能有一个逻辑,两个等待线程都将被唤醒,它们都将检查标志设置为 false,并返回对象? 或者说是矛盾的?

最简单的方法是将等待更改为 if 语句,但这很容易受到虚假唤醒的影响。

【问题讨论】:

  • 横向建议:查看java.util.concurrent 并使用适当的更高级别的构造。
  • 我在考虑使用future,但无论如何实现都需要这样的代码。
  • 怎么样?让所有线程等待同一个Future 似乎就足够了。 (这很难说,因为您没有展示用例。)您可能需要稍微重构您的代码,以便数据流基于从生产者到消费者的“拉”对象,但这通常是一个好主意。
  • 未来的代码将几乎相同。 check 将是 Future.isDone() - 但带有否定。而 return 和 set 将返回并将 done 值设置为 true。
  • 你到底想达到什么目标?

标签: java multithreading concurrency wait notify


【解决方案1】:

您可以使用CountDownLatchCyclicBarrier

使用Future 也是一种可能,更具体的是FutureTask。它有一个方便的方法get(),可用于阻止代码执行,直到 Future 完成其工作,从而满足您的要求。

您还可以实现自己的屏障,它会在循环中执行wait(),直到满足特定条件。满足该条件将触发notifyAll(),循环将完成并且所有线程都可以继续。但这将是重新发明轮子。

【讨论】:

  • 为什么需要FutureTaskExecutorService.submit() 无论如何都会为您创建它,并且您需要的所有方法都通过 Future 接口公开。
  • @millimoose 谁说过ExecutorService? FutureTask 是一个类,而不是一个接口;它拥有 OP 想要的所有必要代码(protected done()public get())。
【解决方案2】:

据我了解,如果您的 condition.await() 返回,您需要从所有线程中的方法体返回。 这个 ugly 解决方案应该会有所帮助,尽管我认为有更好的方法来解决这个问题:

public Object getObject() {
  lock.lock();
  try {
    int localstate = this.state;

    while (check && localstate == this.state)) {
      condition.await(); // all threads that are waiting here have the same state
    }

    if (!check) {
      this.state++; // first thread will change state thus making other threads ignore the 'check' value
    }

    return returnObjectAndSetCheckToFalse();
  } finally {
    lock.unlock();
  }
}

【讨论】:

  • 好吧,看来它实际上可以解决我的问题。状态是 atomicInt/Long ofc。
  • 如果状态在锁之间具有读/写访问权限,则 int 就足够了。
【解决方案3】:

我认为您正在尝试实现的目标是使用 Futures 完成的:

ExecutorService executor = Executors.newCachedThreadPool();

// producer
final Future<String> producer = executor.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        Thread.sleep(5000);
        return "done";
    }
});

// consumers
for (int i = 0; i < 3; i++) {
    final int _i = i;
    executor.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println("Consumer "+_i+" starts.");
            try {
                String value = producer.get();
                System.out.println("Consumer "+_i+" ends: "+value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

如果您运行此程序,您应该看到所有消费者线程打印出他们的开始消息,然后暂停,然后消费者线程打印出他们已经完成。显然,您必须将产生getObject() 值的任何内容更改为Callable,但我敢打赌,这将简化代码,因为现在它将按程序进行结构化,而不是将计算结果存储在一个共享变量。与使用手动锁定的任何代码相比,我也更有信心它是线程安全的。

【讨论】:

    【解决方案4】:

    一种方法是使用wait() 而不是condition.await()。然后使用notifyAll() 唤醒线程。

    理想情况下,您将继续使用导致线程休眠的条件对象并调用方法signalAll() 来唤醒所有线程。

    在你的代码中,我只想添加:

    public Object getObject(){
    lock.lock();
    try {
        while (check)){
            condition.await();
        }
            condition.signalAll();
        return returnObjectAndSetCheckToFalse();
    } finally {
        lock.unlock();
    }
    

    }

    我什至会考虑在 returnObjectAndSetCheckToFalse() 方法中而不是在 return 语句之前使用 condition.signalAll() 的可能性。

    【讨论】:

    • 反正也没用。线程需要在 while 循环检查条件中等待(无论采用何种方法)。收到通知后,第一个线程无论如何都会更改条件,其他较慢的线程将再次阻塞在循环中。
    • 为什么要更低级别?顺便说一句,条件有一个signalAll,相当于notifyAll
    • @assylias 这是真的。我忽略了这一点。让我编辑我的答案。
    • @gomul 问题是即使你改变了检查标志,只有没有被告知等待的线程才会继续执行。您肯定需要通知正在等待的线程。收到通知后哪个线程将首先继续运行,很大程度上取决于 Java 如何实现它们的线程。当我在做 Java 并发时,我从来没有看到线程速度是恒定的,这对我来说是非常随意的。
    • 您的代码将无法正常工作。可能会发生一个线程调用 signalAll 然后立即 returnObjectAndSetCheckToFalse 而第二个线程被唤醒但尚未检查条件的线程。当第一个线程已经更改 check_value 时,第二个线程将检查它
    【解决方案5】:

    确实是矛盾的。你想要达到的目标是有问题的。您希望等待条件的线程应该得到结果并继续,但在通知之后调用getObject 的线程不会。至少,这是不公平的。该线程是否在通知之前设法调用getObject,纯属随机。你应该减少不确定性,而不是增加它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-01-05
      • 2015-07-05
      • 1970-01-01
      • 1970-01-01
      • 2020-08-31
      • 2010-10-29
      • 1970-01-01
      相关资源
      最近更新 更多