我有一个场景,我必须为同一个可调用对象异步执行 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 :您正在将任务提交到
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 为什么还需要一个?
它们之间实际上是有区别的。由于某种原因,
invokeAll()
将为每个生成的get()
调用future
。因此,它将等待任务完成,这就是为什么它可能抛出 InterruptedException
(而 submit()
不抛出任何内容)。
这是
invokeAll()
方法的 Javadoc:
执行给定的任务,返回包含其状态和结果的 Future 列表全部完成时。
因此,两种策略基本上都是相同的,但是如果您调用
invokeAll()
,您将被阻止,直到所有任务完成为止。
invokeAll()
方法正是针对此类情况。你绝对应该使用它。
不过,您实际上并不需要实例化它:
List
这应该足够了,而且它看起来比第一个选择干净得多。
例如,您有 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 毫秒。
List<Futures> futures = service.invokeall(tasks, 100, TimeUnit.MILLISECONDS)
for(Future future: futures) {
if(!future.isCancelled()) {
results.add(future.get());
}
}
- 阻塞调用,当下一行代码取决于提交的任务时使用。
invokeAll()
- 非阻塞调用,当下一行代码不依赖于提交的任务时使用。
性能影响 - 没有硬性和快速的差异化因素。一种方法比另一种方法更好,具体取决于您的用途。有时,为了获得正确的结果,阻塞调用是必要的;在其他情况下,您可以在检查任务是否完成之前继续一些其他工作。