【问题标题】:Whether to use invokeAll or submit - java Executor service是使用invokeAll还是submit - java Executor服务
【发布时间】:2023-03-10 01:31:01
【问题描述】:

我有一个场景,我必须为同一个可调用对象异步执行 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. 首选的方式应该是什么?
  2. 与另一个相比,它们中的任何一个是否存在任何劣势或性能影响?

【问题讨论】:

    标签: java concurrency executorservice java.util.concurrent


    【解决方案1】:

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

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

    首选的方式应该是什么?

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

    1. 如果您不想在任务 submit() 之后等待ExecutorService,请首选Option 1
    2. 如果您需要等待已提交给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 提交所需任务后,我可以处理其他任务
    • 是的,如果您在其他线程工作时确实在主线程中有其他任务要做,那么使用提交会更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-13
    • 1970-01-01
    • 2013-01-02
    • 1970-01-01
    • 2013-01-01
    • 2018-07-17
    • 2012-04-16
    相关资源
    最近更新 更多