ExecutorCompletionService を使用すると、一連のタスクをCallable
sとして送信し、CompletionService
と対話する結果をqueue
として取得できます。
ただし、タスクのinvokeAll
を受け入れるExecutorService
のCollection
もあり、結果を取得するためにFuture
のリストを取得します。
私が知る限り、一方または他方を使用しても利点はありません(for
を使用するinvokeAll
ループを回避することを除いて、submit
を使用する必要があります) CompletionService
)へのタスクと、基本的にそれらは同じアイデアであり、わずかな違いがあります。
では、なぜ一連のタスクを送信するのに2つの異なる方法があるのでしょうか?私はそれらが同等であるという点でパフォーマンスを修正していますか?一方が他方よりも適している場合はありますか?考えられません。
ExecutorCompletionService.poll/take
を使用すると、Future
sが完了したときに、完了順に(多少なりとも)受け取ります。 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 の両方で完全なコードを見つけることができます。
ExecutorCompletionService
を使用すると、各ジョブが完了するとすぐに通知を受けることができます。これに対して、ExecutorService.invokeAll(...)
は、Future
sのコレクションを返す前に、ジョブの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));
}
ExecutorCompletionServiceを実際に使用したことはありませんが、これが「通常の」ExecutorServiceよりも便利な場合は、完了したタスクのFutureを完了順に受信したい場合です。 invokeAllを使用すると、任意の時点で不完全なタスクと完了したタスクが混在したリストを取得できます。
結果の順序のみを考慮して比較する:
送信されたジョブが終了するたびにCompletionService
を使用すると、結果がキューにプッシュされます(完了順序)。その後、送信されたジョブと返された結果の順序は同じではなくなります。したがって、タスクが実行された順序が心配な場合は、CompletionService
を使用してください
As invokeAll
は、タスクを表すFutureのリストを、指定されたタスクリストの反復子によって生成された順序と同じ順序で返します。それぞれが完了しています。