超実用的な高同時実行プログラミング ExecutorCompletionService のケース分析とソース コードの解釈

説明

計算が必要なタスクが多数ある場合、タスクのバッチ全体の実行効率を向上させるために、スレッド プールを使用して非同期計算タスクをスレッド プールに継続的に送信できます。 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 を返します。

おすすめ

転載: blog.csdn.net/dyuan134/article/details/130243449
おすすめ