是使用invokeAll还是submit - java Executor服务

Posted

技术标签:

【中文标题】是使用invokeAll还是submit - java Executor服务【英文标题】:Whether to use invokeAll or submit - java Executor service 【发布时间】:2016-03-30 04:18:08 【问题描述】:

我有一个场景,我必须为同一个可调用对象异步执行 5 个线程。据我了解,有两种选择:

1) 使用提交(可调用)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = new ArrayList<>();
for(Callable callableItem: myCallableList)
    futures.add(executorService.submit(callableItem));

2) 使用invokeAll(Collections of Callable)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));
    首选的方式应该是什么? 与另一个相比,它们中的任何一个是否存在任何劣势或性能影响?

【问题讨论】:

【参考方案1】:

选项 1:您正在将任务提交给ExecutorService,并且您不是在等待所有已提交给ExecutorService的任务完成

选项2:您正在等待完成所有已提交给ExecutorService的任务。

首选的方式应该是什么?

根据应用要求,首选其中任何一个。

    如果您不想在任务 submit() 之后等待ExecutorService,请首选Option 1。 如果您需要等待已提交给ExecutorService的所有任务完成,请首选Option 2

与另一个相比,它们中的任何一个是否有任何劣势或性能影响?

如果您的应用程序需要选项 2,则与选项 1 不同,您必须等待提交给ExecutorService 的所有任务完成。性能不是比较标准,因为两者都是为两个不同的目的而设计的。

还有一件更重要的事情:无论您喜欢什么选项,FutureTask 都会在任务执行期间吞下异常。你必须要小心。看看这个 SE 问题:Handling Exceptions for ThreadPoolExecutor

使用 Java 8,您还有一个选择:ExecutorCompletionService

一个 CompletionService,它使用提供的 Executor 来执行任务。此类安排提交的任务在完成后放置在使用 take 可访问的队列中。该类足够轻量级,适合在处理任务组时临时使用。

查看相关的 SE 问题:ExecutorCompletionService? Why do need one if we have invokeAll?

【讨论】:

@downvoter,再次阅读问题和答案以验证您的判断。 您对选项 2 说过以下内容:“您正在等待所有任务的完成”。 “等待”是什么意思?因为doc 没有说“等待”。你的意思是说,如果我们在调用invokeAll() 之后submit() 对同一个ExecutorService 有更多的任务,这些任务将被延迟到当前调用的任务列表完全执行? 阅读invokeAll的文档:docs.oracle.com/javase/7/docs/api/java/util/concurrent/… => 执行给定的任务,返回一个Futures列表,在所有完成后保存它们的状态和结果 “全部完成后”是否意味着对invokeAll() 的调用被阻塞,直到所有参数Callables 完成执行? 所有任务执行完毕后,invokeAll() 后的下一条语句【参考方案2】:

假设您有一个任务,其结果取决于独立可执行任务的数量。但是要完成初始任务,您只有有限的时间。就像它的 API 调用一样。

例如,您有 100 毫秒的时间来完成***任务,并且还有 10 个相关任务。为此,如果您在此处使用提交,代码将是什么样子。

List<Callable> tasks = []// assume contains sub tasks
List<Future> futures = [] 
for(Callable task: tasks) 
   futures.add(service.submit(task));


for(Future futute: futures) 
    future.get(100, TimeUnit.MILLISECONDS);

因此,如果每个子任务需要 50 毫秒来完成上述代码,则需要 50 毫秒。 但是,如果每个子任务需要 1000 毫秒才能完成,则上述将需要 100 * 10 = 1000 毫秒或 1 秒。这使得计算所有子任务的总时间小于 100 毫秒变得很困难。

invokeAll 方法在这种情况下可以帮助我们

List<Futures> futures = service.invokeall(tasks, 100, TimeUnit.MILLISECONDS)
for(Future future: futures) 
   if(!future.isCancelled()) 
       results.add(future.get());
   

这样,即使单个子任务花费的时间更长,它所花费的最大时间也只有 100 毫秒。

【讨论】:

如果子任务花费了 99 毫秒、199 毫秒、299 毫秒...future.get(100, TimeUnit.MILLISECONDS); 仍然有效,这不是我们想要的。【参考方案3】:

编辑:

它们之间实际上是有区别的。出于某种原因,invokeAll() 将为每个产生的future 调用get()。因此,它将等待任务完成,这就是为什么它可能会抛出 InterruptedException(而 submit() 什么都不抛出)。

这是invokeAll() 方法的Javadoc:

执行给定的任务,全部完成后返回一个保存其状态和结果的 Futures 列表。

所以,这两种策略基本上都是一样的,但是如果你打电话给invokeAll(),你会被阻止,直到所有任务都完成。


原始(不完整)答案:

invokeAll() 方法正好适用于此类情况。你绝对应该使用它。

不过,您实际上并不需要实例化 List

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));

这应该足够了,而且看起来比第一个替代方案更干净。

【讨论】:

如果invokeAll会阻塞直到所有任务都完成,那么提交就不是更好了。向 ExecutorService 提交所需任务后,我可以处理其他任务 是的,如果您在其他线程工作时确实在主线程中有其他任务要做,那么使用提交会更好。

以上是关于是使用invokeAll还是submit - java Executor服务的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 invokeAll() 让所有线程池完成任务?

ExecutorService 使用 invokeAll 和超时异常后可调用线程上的超时未终止

ExecutorService - invokeAll 和 invokeAny 使用场景

ExecutorService.invokeAll 不支持可运行任务的集合

执行者完成服务?如果我们有invokeAll,为啥还需要一个?

为多个任务设置时限invokeAll