web-dev-qa-db-ja.com

ExecutorCompletionService? invokeAllがあるのに、なぜ必要なのですか?

ExecutorCompletionService を使用すると、一連のタスクをCallablesとして送信し、CompletionServiceと対話する結果をqueueとして取得できます。

ただし、タスクのinvokeAllを受け入れるExecutorServiceCollectionもあり、結果を取得するためにFutureのリストを取得します。

私が知る限り、一方または他方を使用しても利点はありません(forを使用するinvokeAllループを回避することを除いて、submitを使用する必要があります) CompletionService)へのタスクと、基本的にそれらは同じアイデアであり、わずかな違いがあります。

では、なぜ一連のタスクを送信するのに2つの異なる方法があるのでしょうか?私はそれらが同等であるという点でパフォーマンスを修正していますか?一方が他方よりも適している場合はありますか?考えられません。

36
Cratylus

ExecutorCompletionService.poll/takeを使用すると、Futuresが完了したときに、完了順に(多少なりとも)受け取ります。 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 の両方で完全なコードを見つけることができます。

68
oldrinb

ExecutorCompletionServiceを使用すると、各ジョブが完了するとすぐに通知を受けることができます。これに対して、ExecutorService.invokeAll(...)は、Futuresのコレクションを返す前に、ジョブのallが完了するのを待ちます。

// 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));
}
18
Gray

ExecutorCompletionServiceを実際に使用したことはありませんが、これが「通常の」ExecutorServiceよりも便利な場合は、完了したタスクのFutureを完了順に受信したい場合です。 invokeAllを使用すると、任意の時点で不完全なタスクと完了したタスクが混在したリストを取得できます。

3
esaj

結果の順序のみを考慮して比較する:

送信されたジョブが終了するたびにCompletionServiceを使用すると、結果がキューにプッシュされます(完了順序)。その後、送信されたジョブと返された結果の順序は同じではなくなります。したがって、タスクが実行された順序が心配な場合は、CompletionServiceを使用してください

As invokeAllは、タスクを表すFutureのリストを、指定されたタスクリストの反復子によって生成された順序と同じ順序で返します。それぞれが完了しています。

1
Dungeon Hunter