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

问题描述 投票:0回答:4

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

1)使用提交(Callable)

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

2)使用invokeAll(可调用集合)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));
  1. 首选方式应该是什么?
  2. 与其他相比,其中任何一个是否有任何缺点或性能影响?
java concurrency executorservice java.util.concurrent
4个回答
32
投票

根据申请要求,优先选择其中之一。

选项 1 :您正在将任务提交到

ExecutorService
,并且您不等待已提交到
ExecutorService

的所有任务完成

用例:如果您不想在任务提交()之后等待

ExecutorService
,则更喜欢
Option 1

主要用于异步请求提交。对于非关键用例,我更喜欢这个选项

示例:假设您已经预订了机票(比如机票)。您想要记录内务处理的请求和响应。日志记录可能位于文件或数据库中。在此用例中,日志记录对于向用户发送付款响应并不重要。将日志记录请求提交给 ExecutorService,该活动将被异步处理。

选项2:您正在等待所有已提交给

ExecutorService
的任务完成。

示例:假设您开发了一款多人游戏。 10 名玩家参与游戏。 5 名玩家输掉了钱。在下一场比赛开始之前,您已为这 5 名玩家提供了重新填充余额的选项。现在要开始下一场比赛,您必须等待所有玩家重新填写请求的结果。然后只有你可以开始下一个游戏。

与其他相比,其中任何一个是否有任何缺点或性能影响?

这取决于业务用例,如上所述。两者都有其优点/缺点。

还有更重要的一件事:无论您喜欢什么选项,

FutureTask
都会在任务执行期间吞掉异常。你必须要小心。看看这个 SE 问题:Handling Exceptions for ThreadPoolExecutor

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

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

看看相关的SE问题:ExecutorCompletionService?如果我们有 invokeAll 为什么还需要一个?


8
投票

编辑:

它们之间实际上是有区别的。由于某种原因,

invokeAll()
将为每个生成的
get()
调用
future
。因此,它将等待任务完成,这就是为什么它可能抛出
InterruptedException
(而
submit()
不抛出任何内容)。

这是

invokeAll()
方法的 Javadoc:

执行给定的任务,返回包含其状态和结果的 Future 列表全部完成时

因此,两种策略基本上都是相同的,但是如果您调用

invokeAll()
,您将被阻止,直到所有任务完成为止。


原始(不完整)答案:

invokeAll()
方法正是针对此类情况。你绝对应该使用它。

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


List

这应该足够了,而且它看起来比第一个选择干净得多。


1
投票

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

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

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

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

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); }

这样,即使个别子任务花费的时间超过这个时间,所需的最长时间也不会超过 100 毫秒。


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

- 阻塞调用,当下一行代码取决于提交的任务时使用。

invokeAll()

- 非阻塞调用,当下一行代码不依赖于提交的任务时使用。

性能影响 - 没有硬性和快速的差异化因素。一种方法比另一种方法更好,具体取决于您的用途。有时,为了获得正确的结果,阻塞调用是必要的;在其他情况下,您可以在检查任务是否完成之前继续一些其他工作。

© www.soinside.com 2019 - 2024. All rights reserved.