灵活的CountDownLatch?

时间:2009-10-28 09:52:17

标签: java multithreading concurrency countdownlatch phaser

我现在遇到了两次问题,即生产者线程生成N个工作项,将它们提交到ExecutorService,然后需要等到所有N个项目都被处理完毕。

注意事项

  • N事先不知道。如果是这样的话,我只需创建一个CountDownLatch然后生成线程await(),直到所有工作完成。
  • 使用CompletionService是不合适的,因为虽然我的生产者线程需要阻止(即通过调用take()),但无法表明所有工作都已完成,导致生产者线程停止等待。

我目前最喜欢的解决方案是使用整数计数器,并在提交工作项时递增,并在处理工作项时递减。在所有N个任务的提交之后,我的生产者线程将需要等待锁定,检查是否通知counter == 0。如果消费者线程已经递减计数器并且新值为0,则消费者线程将需要通知生产者。

是否有更好的方法解决这个问题,或者java.util.concurrent我应该使用合适的构造而不是“自己动手”?

提前致谢。

6 个答案:

答案 0 :(得分:26)

java.util.concurrent.Phaser看起来对你有用。它计划在Java 7中发布,但最稳定的版本可以在jsr166的兴趣小组网站上找到。

移相器是一种美化的循环屏障。您可以注册N个参与方,并在准备好等待特定阶段的预付款时。

关于它如何运作的简单示例:

final Phaser phaser = new Phaser();

public Runnable getRunnable(){
    return new Runnable(){
        public void run(){
            ..do stuff...
            phaser.arriveAndDeregister();
        }
    };
}
public void doWork(){
    phaser.register();//register self
    for(int i=0 ; i < N; i++){
        phaser.register(); // register this task prior to execution 
        executor.submit( getRunnable());
    }
    phaser.arriveAndAwaitAdvance();
}

答案 1 :(得分:2)

您当然可以使用受CountDownLatch保护的AtomicReference,以便您的任务得到包装:

public class MyTask extends Runnable {
    private final Runnable r;
    public MyTask(Runnable r, AtomicReference<CountDownLatch> l) { this.r = r; }

    public void run() {
        r.run();
        while (l.get() == null) Thread.sleep(1000L); //handle Interrupted
        l.get().countDown();
    }
}

注意,任务运行其工作,然后旋转,直到设置倒计时(即知道任务总数)。一旦设置倒计时,它们就会将其倒数并退出。这些提交如下:

AtomicReference<CountDownLatch> l = new AtomicReference<CountDownLatch>();
executor.submit(new MyTask(r, l));

创建/提交作品之后,当您知道创建了多少任务时

latch.set(new CountDownLatch(nTasks));
latch.get().await();

答案 2 :(得分:1)

我已经使用了ExecutorCompletionService这样的东西:

ExecutorCompletionService executor = ...;
int count = 0;
while (...) {
    executor.submit(new Processor());
    count++;
}

//Now, pull the futures out of the queue:
for (int i = 0; i < count; i++) {
    executor.take().get();
}

这涉及保留已提交的任务队列,因此如果您的列表任意长,您的方法可能会更好。

但请确保使用AtomicInteger进行协调,这样您就可以在一个线程中增加它,并在工作线程中减少它。

答案 3 :(得分:0)

我假设您的生产者不需要知道队列何时为空,但需要知道最后一个任务何时完成。

我会向消费者添加waitforWorkDone(producer)方法。生产者可以添加其N个任务并调用wait方法。如果工作队列不为空且当前没有任务正在执行,则wait方法会阻塞传入的线程。

等待锁定的消费者线程notifyAll()如果其任务已完成,则队列为空并且没有其他任务正在执行。

答案 4 :(得分:0)

您所描述的与使用标准信号量非常相似,但使用“向后”。

  • 您的信号量以0许可开始
  • 每个工作单位在完成后发放一个许可
  • 你等待获得N次许可

另外,您可以灵活地获得M&lt;如果要检查中间状态,N许可是有用的。例如,我正在测试一个异步有界消息队列,所以我希望队列在某些M&lt; N所以我可以获得M并检查队列是否已满,然后在消耗队列中的消息后获取剩余的N-M许可。

答案 5 :(得分:0)

使用一次通过StreamPhaser进行精确操作的独立Java 8+方法。 Iterator / Iterable的变体完全相同。

public static <X> int submitAndWait(Stream<X> items, Consumer<X> handleItem, Executor executor) {
    Phaser phaser = new Phaser(1); // 1 = register ourselves
    items.forEach(item -> {
            phaser.register(); // new task
            executor.execute(() -> {
                handleItem.accept(item);
                phaser.arrive(); // completed task
            });
    });
    phaser.arriveAndAwaitAdvance(); // block until all tasks are complete
    return phaser.getRegisteredParties()-1; // number of items
}
...
int recognised = submitAndWait(facesInPicture, this::detectFace, executor)

仅供参考。这对于单个事件很好,但是,如果同时调用该事件,则涉及ForkJoinPool的解决方案将阻止父线程阻塞。