用例:任务是在一个线程中生成的,需要分配给多个线程进行计算,最后生成任务应该获得结果并将任务标记为已完成。
我发现类ExecutorCompletionService
几乎完全符合用例 - 除了我看不到非空闲等待的好方法。让我解释一下。
原则上我的代码看起来像
while (true) {
MyTask t = generateNextTask();
if (t!=null) {
completionService.submit(t);
}
MyTask finished;
while (null!=(finished=compService.poll())) {
retireTaks(finished);
}
}
如果当前没有可用的新任务,并且当前没有任何任务分别从CompletionService返回,generateNextTask()
和completionService.poll()
都可以返回null
。
在这些情况下,循环退化为丑陋的空闲等待。我可以poll()
超时或为{ - 1}}添加Thread.sleep()
,但我认为这是一个糟糕的解决方法,因为它会浪费CPU并且不尽可能地响应,由于等待。
假设我在null
上用generateNextTask()
替换poll()
,是否有好方法轮询队列以及并行BlockingQueue
以唤醒什么样的东西可用?
实际上这让我想起CompletionService
。有类似的东西可用于队列吗?
答案 0 :(得分:0)
您应该使用CompletionService.take()等待下一个任务完成并检索其Future。 poll()是非阻塞版本,如果当前没有任务完成,则返回null。
此外,您的代码似乎效率低下,因为您一次只生成和使用一个任务,而不是允许并行处理多个任务。考虑为任务生成和任务结果消耗使用不同的线程。
- 编辑 -
我认为,鉴于您在评论中提到的限制,您无法达到所有要求。 要求主线程是生产者和消费者,并且不允许任何繁忙的循环或定时循环,您不能避免阻塞等待任务完成的时间太长并且同时没有处理其他任务的情况。
答案 1 :(得分:0)
由于你“可以用BlockingQueue上的poll()替换generateNextTask()”,我假设传入的任务可以被其他一些线程放入队列,问题是,你不能执行take()
on 2个队列同时排队。解决方案是简单地将传入和完成的任务放在同一队列中。要区分,请将它们包装在不同类型的对象中,然后在take()之后在循环中检查该类型。
此解决方案有效,但我们可以更进一步。你说你不想使用2个线程来处理任务 - 那么你可以使用零线程。让包装器实现Runnable,而不是检查类型,只需调用take().run()
即可。这样,您的线程就变成了单线程Executor
。但是我们已经有了Executor(CompletionService),我们可以使用它吗?问题是,传入和完成任务的处理应该是连续进行的,而不是并行进行的。所以我们需要api/java/util/concurrent/Executor中描述的SerialExecutor
,它接受Runnables并连续执行它们,但是在另一个执行器上。这样就不会浪费任何线程。
最后,您提到了Selector作为可能的解决方案。我必须说,这是一种过时的做法。学习数据流和演员计算。好的介绍是here。查看我的Dataflow4java项目,它有MultiPortActorTest.java示例,其中类Accum
执行您需要的操作,所有样板文件都包含在支持库中隐藏的包装器Runnables和串行执行程序。
答案 2 :(得分:0)
你需要的是来自番石榴的ListenableFuture
。 ListenableFutureExplained