説明
計算が必要なタスクが多数ある場合、タスクのバッチ全体の実行効率を向上させるために、スレッド プールを使用して非同期計算タスクをスレッド プールに継続的に送信できます。 Future を各タスクに関連付けたままにし、最後にこれらの Future を走査して、Future インターフェイス実装クラスの get メソッドを呼び出して、コンピューティング タスクのバッチ全体の結果を取得する必要があります。
スレッド プールは全体的な実行効率を向上させるために使用されますが、これらの Future を走査して Future インターフェイス実装クラスの get メソッドを呼び出すことはブロックされます。つまり、現在の Future に関連付けられたコンピューティング タスクが実際に実行されると、get メソッドは戻り値を返します。結果、現在の計算タスクが完了していないが、Future に関連付けられた他の計算タスクが完了している場合、多くの待機時間が無駄になるため、トラバース時に最初に結果を取得するのが最善です。待ち時間。
ExecutorCompletionService はそのような効果を実現できます。実行された Future を保存するための先入れ先出しブロッキング キューが内部にあり、その take メソッドまたは Paul メソッドを呼び出すことで、実行された Future を取得でき、その後、Future インターフェイス実装クラスの get メソッドを呼び出して、最終結果を取得します。
デモの例
@Test
public void test() throws InterruptedException, ExecutionException {
Executor executor = Executors.newFixedThreadPool(3);
CompletionService<String> service = new ExecutorCompletionService<>(executor);
for (int i = 0 ; i < 5 ;i++) {
int seqNo = i;
service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "HelloWorld-" + seqNo + "-" + Thread.currentThread().getName();
}
});
}
for (int j = 0 ; j < 5; j++) {
System.out.println(service.take().get());
}
}
結果:
HelloWorld-2-pool-1-thread-3
HelloWorld-1-pool-1-thread-2
HelloWorld-3-pool-1-thread-2
HelloWorld-4-pool-1-thread-3
HelloWorld-0-pool-1-thread-1
メソッド分析
ExecutorCompletionService は CompletionService インターフェイスを実装しており、次のメソッドが CompletionService インターフェイスで定義されています。
- Future submit(呼び出し可能なタスク): 呼び出し可能なタスクを送信し、タスクの実行結果に関連付けられた Future を返します。
- Future submit(Runnable task, V result): Runnable タスクを送信し、タスクの実行結果に関連付けられた Future を返します。
- Future take(): 最初に完了したタスクを取得して内部ブロッキング キューから削除し、タスクが完了するまでブロックします。
- Future roll(): 最初に完了したタスクを取得して内部ブロッキング キューから削除し、取得できなかった場合はブロックせずに null を返します。
- 今後のポーリング(ロングタイムアウト、TimeUnit単位): 内部ブロッキングキューから最初に完了したタスクを取得して削除します。ブロッキング時間はタイムアウトです。取得できない場合はnullを返します。
ソースコード分析
上記のデモ コード例に従って、ExecutorCompletionService の内部実装原理を分析します。
ExecutorCompletionService には、executor、aes、completionQueue という 3 つのプライベート プロパティがあり、completionQueue は完了したタスクを格納するキューであり、具体的なコードは次の図に示すとおりです。
その構築メソッドを入力し、メソッド内の 3 つの属性に値を割り当てます。ここでは、LinkedBlockingQueue タイプの先入れ先出しブロッキング キューが初期化されていることがわかります。具体的なコードは次の図に示すとおりです。
次に、ExecutorCompletionService の submit メソッドに入りますが、ここではパラメーターの型が Callable である submit メソッドを解析します。具体的なコードは下図のとおりです。
トラッキング コードは newTaskFor メソッドに入ります。具体的なコードは次の図のとおりです。
ExecutorCompletionService構築メソッドではaesに値が代入されているので、AbstractExecutorServiceのnewTaskForメソッドを入力します。具体的なコードは下図のとおりです。
トラッキングコードはFutureTask構築メソッドに入り、具体的なコードは下図のとおりです。
ここで構築した RunnableFuture インスタンス オブジェクトが完成しました。上記の submit メソッドに戻って、executor.execute(new QueueingFuture(f)) の解析を続けます。まずは new QueueingFuture(f) です。QueueingFuture は ExecutorCompletionService の内部クラスです。具体的なコードは次の図のとおりです。
図のコードからわかるように、RunnableFuture インスタンス オブジェクトは、QueueingFuture の task プロパティに割り当てられています。上図の赤枠の中に、done メソッドがあることに注意してください。その内部では、完了したタスクにタスクを追加します。ブロックキュー。これは最初に記録され、後で使用します。次に、executor.execute(new QueueingFuture(f)) を分析します。サンプル デモンストレーション コードでは ThreadPoolExecutor が使用されているため、ThreadPoolExecutor で executor.execute() メソッドが実行されます。具体的なキー コードは次の図に示すとおりです。
ここでは極端なケースは分析しません。ワーカー スレッドの数がコア スレッドの数より少ない場合、addWorker メソッドが実行されます。このメソッドの本体には多くの内容が含まれています。ここではキー コードのみに焦点を当てます。具体的なコードを下の図に示します。
最初の赤いボックス内のコードは Worker インスタンスを構築します。具体的なコードは次の図に示すとおりです。
上図の赤枠内のコードに従って、コードを辿っていくと、上図のrunメソッド内でt.start()メソッドが実行され、runWorkerメソッドが実行されることが分かります。 runメソッド内での具体的なコードは下図のとおりです。
上図のコードをたどっていくと、task.run()を実行すると、先ほど構築したRunnableFutureインスタンスオブジェクトのrunメソッドに入ることがわかり、具体的なコードは下図のとおりです。
最初の赤いボックス内のコードは、実際のタスク実行のコードであり、submit によって送信されたタスクが実際に実行される場所です。2番目の赤枠内のコードが例外発生時の処理、3番目の赤枠内のコードが通常実行で完了する処理で、それぞれの具体的な実装コードは以下のとおりです。
上の 2 つの図のコードから、finishCompletion() メソッドが実行されていることが分かりますので、このメソッドの機能を明らかにしましょう。具体的なコードは下図のとおりです。
上図の赤枠内のコードからわかるように、ここではdone()メソッドが実行されており、実際に実行されるのは、前の分析で述べた完了したブロッキングキューにタスクを追加するdoneメソッドです。これまでのところ、タスクが完了または異常である場合、そのタスクは完了ブロッキング キューに追加され、処理のために取り出されます。
次にExecutorCompletionServiceのtakeメソッドとpollメソッドを解析します(具体的なコードは以下の図)。
上の図からわかるように、すべての操作でブロッキング キューが完了しているため、次の図に示すように、完了したブロッキング キュー内のコードを見てみましょう。
上の図は、実行タスクがループを待つことによって完了することを明確に示しています。
上記のコードはブロックせず、完了した実行タスクがない場合は直接 null を返します。
上記のコードは、指定された時間ブロックし、完了した実行タスクがない場合は直接 null を返します。