ExecutorCompletionService?如果我们有invokeAll,为什么需要一个?

时间:2012-08-08 20:20:21

标签: java multithreading concurrency executorservice java.util.concurrent

如果我们使用ExecutorCompletionService,我们可以提交一系列Callable个任务,并将结果与​​CompletionService作为queue进行交互。

invokeAll ExecutorService Collection接受Future个任务,我们会得到一个for列表来检索结果。

据我所知,使用一个或另一个没有任何好处(除了我们避免invokeAll循环使用submit我们必须CompletionService {{1}})的任务,基本上它们是相同的想法,略有不同。

那为什么有两种不同的方式来提交一系列任务呢?我是否正确表现他们是相同的?是否有一个比另一个更合适的情况?我想不出一个。

4 个答案:

答案 0 :(得分:64)

使用ExecutorCompletionService.poll/take,您将按完成顺序(或多或少)收到Future。使用ExecutorService.invokeAll,你没有这种力量;你要么阻止直到全部完成,要么你指定一个超时,之后不完整被取消。


static class SleepingCallable implements Callable<String> {

  final String name;
  final long period;

  SleepingCallable(final String name, final long period) {
    this.name = name;
    this.period = period;
  }

  public String call() {
    try {
      Thread.sleep(period);
    } catch (InterruptedException ex) { }
    return name;
  }
}

现在,下面我将演示invokeAll的工作原理:

final ExecutorService pool = Executors.newFixedThreadPool(2);
final List<? extends Callable<String>> callables = Arrays.asList(
    new SleepingCallable("quick", 500),
    new SleepingCallable("slow", 5000));
try {
  for (final Future<String> future : pool.invokeAll(callables)) {
    System.out.println(future.get());
  }
} catch (ExecutionException | InterruptedException ex) { }
pool.shutdown();

这会产生以下输出:

C:\dev\scrap>java CompletionExample
... after 5 s ...
quick
slow

使用CompletionService,我们会看到不同的输出:

final ExecutorService pool = Executors.newFixedThreadPool(2);
final CompletionService<String> service = new ExecutorCompletionService<String>(pool);
final List<? extends Callable<String>> callables = Arrays.asList(
    new SleepingCallable("slow", 5000),
    new SleepingCallable("quick", 500));
for (final Callable<String> callable : callables) {
  service.submit(callable);
}
pool.shutdown();
try {
  while (!pool.isTerminated()) {
    final Future<String> future = service.take();
    System.out.println(future.get());
  }
} catch (ExecutionException | InterruptedException ex) { }

这会产生以下输出:

C:\dev\scrap>java CompletionExample
... after 500 ms ...
quick
... after 5 s ...
slow

请注意,时间与程序启动有关,而不是之前的消息。


您可以在here上找到完整的代码。

答案 1 :(得分:17)

通过使用ExecutorCompletionService,您可以在每个作业完成时立即收到通知。相比之下,ExecutorService.invokeAll(...)在返回Future s的集合之前等待所有的作业完成:

// this waits until _all_ of the jobs complete
List<Future<Object>> futures = threadPool.invokeAll(...);

相反,当您使用ExecutorCompletionService时,您将能够在每个作业完成后立即获取作业,这允许您(例如)将它们发送到另一个线程池进行处理,记录结果等等。

ExecutorService threadPool = Executors.newFixedThreadPool(2);
ExecutorCompletionService<Result> compService
      = new ExecutorCompletionService<Result>(threadPool);
for (MyJob job : jobs) {
    compService.submit(job);
}
// shutdown the pool but the jobs submitted continue to run
threadPool.shutdown();
while (!threadPool.isTerminated()) {
    // the take() blocks until any of the jobs complete
    // this joins with the jobs in the order they _finish_
    Future<Result> future = compService.take();
    // this get() won't block
    Result result = future.get();
    // you can then put the result in some other thread pool or something
    // to immediately start processing it
    someOtherThreadPool.submit(new SomeNewJob(result));
}

答案 2 :(得分:2)

我还没有真正使用过ExecutorCompletionService,但我认为这可能比#34; normal&#34;更有用。 ExecutorService将是您希望按完成顺序接收已完成任务的期货。使用invokeAll,您只需获得一个列表,该列表可以包含任何给定时间的未完成和已完成任务的混合。

答案 3 :(得分:1)

仅考虑结果顺序进行比较:

当我们在提交的作业完成时使用CompletionService时,结果将被推送到队列(完成订单)。然后,提交的作业的顺序和返回的结果不再相同。因此,如果您关注执行任务的顺序,请使用CompletionService

As invokeAll返回表示任务的Futures列表,其顺序与给定任务列表的迭代器生成的顺序相同,每个都已完成。