ExecutorsクラスのnewFixedThreadPoolメソッドは、固定数のスレッドを持つexecutorServiceを作成します。newSingleThreadExecutorメソッドとは異なり、プール内に必要なスレッドの数を指定します。内部では次のコードが呼び出されます。

new ThreadPoolExecutor(nThreads, nThreads,
                                      	0L, TimeUnit.MILLISECONDS,
                                      	new LinkedBlockingQueue());

corePoolSize (エグゼキューターサービスの開始時に準備が完了する (開始される) スレッドの数)パラメーターとMaximumPoolSize (エグゼキューターサービスが作成できるスレッドの最大数) パラメーターは同じ値、つまりnewFixedThreadPool(nThreadsに渡されるスレッドの数) を受け取ります。 。また、まったく同じ方法で独自のThreadFactory実装を渡すことができます。

さて、なぜこのようなExecutorService が必要なのかを見てみましょう。

固定数 (n) のスレッドを使用したExecutorServiceのロジックは次のとおりです。

  • タスクの処理には最大 n 個のスレッドがアクティブになります。
  • n 個を超えるタスクが送信された場合、スレッドが空になるまでタスクはキューに保持されます。
  • スレッドの 1 つが失敗して終了すると、代わりに新しいスレッドが作成されます。
  • プール内のすべてのスレッドは、プールがシャットダウンされるまでアクティブです。

例として、空港で保安検査を通過するのを待っていることを想像してください。セキュリティチェックの直前まで全員が一列に並び、乗客は作業中のすべてのチェックポイントに分配されます。いずれかのチェックポイントで遅延がある場合、最初のチェックポイントが空くまで、キューは 2 番目のチェックポイントによってのみ処理されます。また、1 つの検問所が完全に閉鎖された場合は、代わりに別の検問所が開設され、乗客は引き続き 2 つの検問所を経由して処理されます。

たとえ条件が理想的であっても (約束された n 個のスレッドが安定して動作し、エラーで終了したスレッドは常に置き換えられます (リソースが限られているため、実際の空港では実現不可能です))、システムにはまだいくつかのスレッドがあることがすぐにわかります。不快な機能は、スレッドがタスクを処理できる速度を超えてキューが増加したとしても、いかなる状況でもスレッドが増えることはないからです。

ExecutorService が固定数のスレッドでどのように動作するかを実際に理解することをお勧めします。Runnableを実装するクラスを作成しましょう。このクラスのオブジェクトは、 ExecutorServiceのタスクを表します。

public class Task implements Runnable {
    int taskNumber;

    public Task(int taskNumber) {
        this.taskNumber = taskNumber;
    }

    @Override
    public void run() {
try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
    }
}

run()メソッドでは、スレッドを 2 秒間ブロックしてワークロードをシミュレートし、現在のタスクの番号とタスクを実行しているスレッドの名前を表示します。

ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 30; i++) {
            executorService.execute(new Task(i));
        }

        executorService.shutdown();

まず、mainメソッドでExecutorServiceを作成し、30 個のタスクを実行のために送信します。

プール-1-スレッド-2 スレッドでユーザー リクエスト
#1 を処理しました プール-1-スレッド-1 スレッドで
ユーザー リクエスト #0 を処理しました プール-1-スレッド-3 スレッドでユーザー リクエスト #2 を処理しました プール-で
ユーザー リクエスト #5 を処理しました1 スレッド 3 スレッド
プール 1 スレッド 2 スレッドでユーザー リクエスト #3 を処理 プール
1 スレッド 1 スレッドでユーザー リクエスト #4 を処理 プール 1 スレッド1 スレッドで ユーザー リクエスト
#8 を処理
プール-1-スレッド-3 スレッドのリクエスト #6 プール
-1-スレッド-2 スレッドでユーザー リクエスト #7 を処理
プール-1-スレッド-3 スレッドでユーザー リクエスト #10 を
処理 プール-1- でユーザー リクエスト #9 を処理thread-1 スレッド
プール 1-スレッド 2 スレッドでユーザー リクエスト #11 を処理しました
プール 1-スレッド 3 スレッドでユーザー リクエスト #12 を処理しました
プール-1-スレッド-2 スレッドでユーザー リクエスト #14 を処理しました プール-1-
スレッド-1 スレッドで
ユーザー リクエスト #13 を処理しました プール-1-スレッド-3 スレッドでユーザー リクエスト #15 を
処理しました プール-でユーザー リクエスト #16 を処理しました1 スレッド 2 スレッド
プール 1 スレッド 1 スレッドでユーザー リクエスト #17 を処理
プール 1 スレッド 3 スレッド
でユーザー リクエスト #18 を処理 プール 1 スレッド 2 スレッドで
ユーザー リクエスト #19 を処理プール-1-スレッド-1 スレッドでリクエスト #20
プール-1-スレッド-3 スレッドでユーザー リクエスト
#21 を処理 プール-1-スレッド-2 スレッドでユーザー リクエスト #22 を
処理 プール-1-でユーザー リクエスト #23 を処理thread-1 スレッド
プール 1-スレッド 2 スレッドでユーザー リクエスト #25 を処理しました
プール 1-スレッド 3 スレッドでユーザー リクエスト #24 を処理しました
プール-1-スレッド-1 スレッドでユーザー リクエスト #26 を処理しました
プール-1-スレッド-2 スレッドで
ユーザー リクエスト #27 を処理しました プール-1-スレッド-3 スレッドでユーザー リクエスト #28 を処理しました プール
-でユーザー リクエスト #29 を処理しました1-スレッド-1 スレッド

コンソール出力には、前のタスクによってタスクが解放された後、タスクが別のスレッドでどのように実行されるかが示されます。

ここで、タスクの数を 100 に増やし、100 個のタスクを送信した後、 awaitTermination(11, SECONDS)メソッドを呼び出します。数値と時間単位を引数として渡します。このメソッドはメインスレッドを 11 秒間ブロックします。次に、 shutdownNow()を呼び出して、すべてのタスクが完了するのを待たずにExecutorServiceを強制的にシャットダウンします。

ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task(i));
        }

        executorService.awaitTermination(11, SECONDS);

        executorService.shutdownNow();
        System.out.println(executorService);

最後に、executorServiceの状態に関する情報を表示します。

得られるコンソール出力は次のとおりです。

プール-1-スレッド-1 スレッドでユーザー リクエスト #0 を処理しました
プール-1-スレッド-3 スレッドで
ユーザー リクエスト #2 を処理しました プール-1-スレッド-2 スレッドでユーザー リクエスト #1 を処理しました プール-で
ユーザー リクエスト #4 を処理しました1 スレッド 3 スレッド
プール 1 スレッド 2 スレッドでユーザー リクエスト #5 を処理 プール
1 スレッド 1 スレッド
でユーザー リクエスト #3 を処理 プール 1 スレッド 3 スレッドで
ユーザー リクエスト #6 を処理プール-1-スレッド-2 スレッドのリクエスト #7 プール
-1-スレッド-1 スレッドでユーザー リクエスト #8 を処理
プール-1-スレッド-3 スレッドでユーザー リクエスト #9 を
処理 プール-1- でユーザー リクエスト #11 を処理thread-1 スレッド
プール 1-スレッド 2 スレッドでユーザー リクエスト #10 を処理しました プール
1-スレッド 1 スレッドでユーザー リクエスト #13 を処理しました
プール 1 スレッド 2 のスレッドでユーザー リクエスト #14 を処理しました
プール 1 スレッド 3 のスレッドでユーザー リクエスト #12 を処理しました
java.util.concurrent.ThreadPoolExecutor@452b3a41[シャットダウン中、プール サイズ = 3、アクティブ スレッド = 3 、キューに入れられたタスク = 0、完了したタスク = 15]
プール 1 スレッド 3 のスレッドでユーザー リクエスト #17 を処理しました
プール 1 スレッド 1 のスレッドでユーザー リクエスト #15 を処理しました プール 1 スレッド
でユーザー リクエスト #16 を処理しました-2スレッド

これに続いて、 3 つのアクティブなタスクからのスリープメソッドによってスローされる3 つのInterruptedExceptions が発生します。

プログラムが終了すると 15 個のタスクが完了しますが、プールにはまだタスクの実行を完了していない 3 つのアクティブなスレッドが残っていることがわかります。これら 3 つのスレッドで中断()メソッドが呼び出されます。これはタスクが完了することを意味しますが、この例では、スリープメソッドがInterruptedExceptionをスローします。また、 shutdownNow()メソッドが呼び出された後、タスク キューがクリアされることもわかります。

したがって、プール内のスレッド数が固定されたExecutorService を使用する場合は、その仕組みを必ず覚えておいてください。このタイプは、既知の一定負荷のタスクに適しています。

もう 1 つの興味深い質問があります。単一のスレッドにエグゼキュータを使用する必要がある場合、どのメソッドを呼び出す必要がありますか? newSingleThreadExecutor()またはnewFixedThreadPool(1) ?

どちらのエグゼキュータも同等の動作をします。唯一の違いは、newSingleThreadExecutor()メソッドは、追加のスレッドを使用するように後で再構成できないエグゼキューターを返すことです。