同じ呼び出し可能オブジェクトに対して5つのスレッドを非同期で実行する必要があるシナリオがあります。私が理解している限り、2つのオプションがあります。
1)submit(Callable)を使用する
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = new ArrayList<>();
for(Callable callableItem: myCallableList){
futures.add(executorService.submit(callableItem));
}
2)invokeAll(Callablesのコレクション)を使用する
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));
オプション1:タスクをExecutorService
に送信し、送信されたすべてのタスクの完了を待機していませんExecutorService
オプション2:ExecutorService
に送信されたすべてのタスクの完了を待っています。
何が望ましい方法でしょうか?
アプリケーションの要件に応じて、どちらかが推奨されます。
ExecutorService
になってから待機したくない場合は、Option 1
。ExecutorService
に送信されたすべてのタスクの完了を待つ必要がある場合は、Option 2
。他のものと比較して、それらのいずれかに不利な点やパフォーマンスへの影響はありますか?
アプリケーションがオプション2を要求する場合、オプション1とは異なり、ExecutorService
に送信されたすべてのタスクが完了するまで待つ必要があります。パフォーマンスは、両方が2つの異なる目的のために設計されているため、比較の基準ではありません。
そしてもう1つの重要なこと:どちらのオプションでも、FutureTask
はタスクの実行中に例外を飲み込みます。あなたは注意する必要があります。このSEの質問を見てください: ThreadPoolExecutorの例外の処理
Java 8の場合、もう1つのオプションがあります: ExecutorCompletionService
提供されたエグゼキューターを使用してタスクを実行するCompletionService。このクラスは、送信されたタスクが完了時に、takeを使用してアクセス可能なキューに配置されるように調整します。このクラスは軽量で、タスクのグループを処理する際の一時的な使用に適しています。
関連するSEの質問を見てください: ExecutorCompletionService?invokeAllがあるのになぜ必要なのですか?
実際には違いがあります。何らかの理由で、invokeAll()
は、生成されるfuture
ごとにget()
を呼び出します。したがって、タスクが完了するまで待機するため、InterruptedException
がスローされる可能性があります(submit()
は何もスローしません)。
これがinvokeAll()
メソッドのJavadocです。
指定されたタスクを実行し、ステータスと結果を保持するFutureのリストを返しますすべて完了したとき。
したがって、どちらの戦略も基本的に同じですが、invokeAll()
を呼び出すと、すべてのタスクが完了するまでブロックされます。
invokeAll()
メソッドは、このような状況にぴったりです。あなたは間違いなくそれを使うべきです。
ただし、実際にList
をインスタンス化する必要はありません。
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));
これで十分なはずですし、最初の選択肢よりもずっときれいに見えます。
独立して実行可能なタスクの数に結果が依存するタスクがあるとします。しかし、最初のタスクを完了するには、限られた時間しかありません。その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ミリ秒以内です。