我现在遇到了两次问题,即生产者线程生成N个工作项,将它们提交到ExecutorService
,然后需要等到所有N个项目都被处理完毕。
注意事项
CountDownLatch
然后生成线程await()
,直到所有工作完成。CompletionService
是不合适的,因为虽然我的生产者线程需要阻止(即通过调用take()
),但无法表明所有工作都已完成,导致生产者线程停止等待。我目前最喜欢的解决方案是使用整数计数器,并在提交工作项时递增,并在处理工作项时递减。在所有N个任务的提交之后,我的生产者线程将需要等待锁定,检查是否通知counter == 0
。如果消费者线程已经递减计数器并且新值为0,则消费者线程将需要通知生产者。
是否有更好的方法解决这个问题,或者java.util.concurrent
我应该使用合适的构造而不是“自己动手”?
提前致谢。
答案 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)
您所描述的与使用标准信号量非常相似,但使用“向后”。
另外,您可以灵活地获得M&lt;如果要检查中间状态,N许可是有用的。例如,我正在测试一个异步有界消息队列,所以我希望队列在某些M&lt; N所以我可以获得M并检查队列是否已满,然后在消耗队列中的消息后获取剩余的N-M许可。
答案 5 :(得分:0)
使用一次通过Stream
对Phaser
进行精确操作的独立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
的解决方案将阻止父线程阻塞。