是使用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()
的调用被阻塞,直到所有参数Callable
s 完成执行?
所有任务执行完毕后,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服务的主要内容,如果未能解决你的问题,请参考以下文章
ExecutorService 使用 invokeAll 和超时异常后可调用线程上的超时未终止
ExecutorService - invokeAll 和 invokeAny 使用场景
ExecutorService.invokeAll 不支持可运行任务的集合