Java 如何唤醒在相同条件下等待的所有线程?

Java 如何唤醒在相同条件下等待的所有线程?,java,multithreading,concurrency,wait,notify,Java,Multithreading,Concurrency,Wait,Notify,我有一个下面的场景。多个线程在相同条件下等待。当收到通知时,所有人应停止等待,更改标志并返回对象: public Object getObject(){ lock.lock(); try { while (check)){ condition.await(); } return returnObjectAndSetCheckToFalse(); } finally { lock.u

我有一个下面的场景。多个线程在相同条件下等待。当收到通知时,所有人应停止等待,更改标志并返回对象:

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

        return returnObjectAndSetCheckToFalse();
    } finally {
        lock.unlock();
    }
}
然而,这段代码不起作用,因为更快的线程可能会将check标志更改为false,而第二个较慢的线程将再次阻塞。 可能有这样一种逻辑:两个等待线程都将被唤醒,它们都将check flag设置为false,并返回object? 或者这是矛盾的

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

您可以使用或

使用
未来
也是一种可能,更具体地说是
未来任务
。它有一个conveniance方法
get()
,可用于阻止代码执行,直到将来完成其工作,从而满足您的需求


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

我认为您正试图实现的是,使用
Future
s:

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();
            }
        }
    });
}
ExecutorService executor=Executors.newCachedThreadPool();
//制作人
最终未来生产者=执行者。提交(新可调用(){
@凌驾
公共字符串调用()引发异常{
睡眠(5000);
返回“完成”;
}
});
//消费者
对于(int i=0;i<3;i++){
最终积分i=i;
执行者提交(新的可运行(){
@凌驾
公开募捐{
System.out.println(“消费者”+_i+“启动”);
试一试{
字符串值=producer.get();
System.out.println(“消费者”+_i+”结束:“+值”);
}捕获(例外e){
e、 printStackTrace();
}
}
});
}

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

一种方法是使用而不是
条件。wait()
。然后使用唤醒线程

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

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

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


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

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

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();
  }
}

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

横向建议:查看
java.util.concurrent
并使用适当的更高级别构造。我曾考虑使用future,但实现无论如何都需要这样的代码。怎么会这样?让所有线程都在同一个
未来
上等待似乎就足够了。(很难说,因为您还没有展示用例。)您可能需要重新构造代码,以便数据流基于将对象从生产者“拉”到消费者,但这通常是一个好主意。将来的代码将几乎相同。check将是Future.isDone()-但带有否定。而return和set将返回并将done值设置为true。您到底想要实现什么?为什么需要
FutureTask
ExecutorService.submit()
无论如何都会为您创建它,您需要的所有方法都会通过
Future
界面公开。@millimoose谁说过
ExecutorService
?FutureTask是一个类,而不是一个接口;它具有OP所需的所有必要代码(
protecteddone()
public get()
)。无论如何,它都没有帮助。线程需要在while循环检查条件下等待(无论采用何种方法)。在收到通知后,第一个线程仍将更改条件,而其他较慢的线程将再次阻塞在循环中。为什么要执行更低的级别?顺便说一下,条件有一个
signalAll
,它相当于
notifyAll
@assylias,这是真的。我忽略了这一点。让我编辑我的答案。@gomul问题是,即使您更改了check标志,也只有没有被告知等待的线程将继续执行。您肯定需要通知正在等待的线程。在收到通知后,哪个线程将首先继续,这取决于Java如何实现它们的线程。当我在做Java并发时,我从来没有看到线程速度是恒定的,这对我来说是非常任意的。你的代码不会以任何方式工作。可能会发生这样的情况:一个线程调用了signalAll,然后立即返回ObjectAndSetCheckToFalse,而第二个线程被唤醒,但尚未检查条件。第二个线程将在第一个线程已经更改了check_valuewell时检查它,并推测它实际上可能解决了我的问题。状态为atomicInt/Long ofc.int,如果状态在lo之间具有读/写访问权限,则该值足够