Java
 Computer >> コンピューター >  >> プログラミング >> Java

固定スレッドプールとキャッシュスレッドプールの違いを徹底解説

JavaのExecutorフレームワークは、スレッドプールという概念をもとに設計されています。スレッドプールとは、タスクを実行するたびに新しいスレッドを生成するのではなく、すでに作成済みのスレッドを再利用することで、オーバーヘッドを削減しパフォーマンスを向上させる仕組みです。

Executorsクラスは、スレッドプールを生成するためのファクトリメソッドを提供しており、その多くが返すエグゼキュータの基底実装となっているのがThreadPoolExecutorクラスです。

固定スレッドプールとキャッシュスレッドプールの比較

固定スレッドプール(newFixedThreadPool)とキャッシュスレッドプール(newCachedThreadPool)の主な違いを、以下の表にまとめました。

番号項目固定スレッドプール(Fixed Thread Pool)キャッシュスレッドプール(Cached Thread Pool)
1 基本動作 Javadocより ― 共有された無制限キューで動作する、固定数のスレッドを再利用するスレッドプールを作成します。どの時点でも、最大 nThreads 個のスレッドのみがアクティブにタスクを処理します。すべてのスレッドが稼働中に追加のタスクが提出された場合、そのタスクはスレッドが空くまでキュー内で待機します。シャットダウン前にスレッドが障害によって終了した場合は、後続のタスクを実行するために必要であれば新しいスレッドが代わりに起動されます。プール内のスレッドは、明示的に shutdown() が呼ばれるまで存在し続けます。 Javadocより ― 必要に応じて新しいスレッドを作成しますが、利用可能な場合は以前に作成済みのスレッドを再利用します。この種のプールは、短時間で完了する非同期タスクを大量に実行するプログラムのパフォーマンスを通常向上させます。execute() の呼び出し時には、利用可能な既存スレッドがあればそれを再利用し、なければ新しいスレッドを作成してプールに追加します。
2 使用するキュー BlockingQueue(ブロッキングキュー)を使用します。 SynchronousQueue(同期キュー)を使用します。
3 スレッドの寿命 明示的に終了(shutdown)されるまで、すべてのスレッドが稼働し続けます。 60秒間使用されなかったスレッドは終了され、キャッシュから削除されます。
4 スレッドプールサイズ サイズは固定されており、拡張されません。 0から Integer.MAX_VALUE まで拡張される可能性があります。
5 ユースケース 同時に実行できるタスク数を制限したい場合に適しています。 短時間で完了するタスクが大量に発生する、予測可能なワークロードに適しています。

固定スレッドプールのサンプルコード

次の例では、4つのスレッドを持つ固定スレッドプールを作成し、CountDownLatch を使って2つのサービス処理の完了を待ち合わせています。

public class Main {
    public static void main(String args[]) throws InterruptedException {
        ExecutorService executors = Executors.newFixedThreadPool(4);
        CountDownLatch latch = new CountDownLatch(2);

        executors.submit(new Service1(latch));
        executors.submit(new Service2(latch));

        latch.await();
        System.out.println("Done");
    }
}

import java.util.concurrent.CountDownLatch;

public class Service1 implements Runnable {
    CountDownLatch latch;

    public Service1(CountDownLatch latch) {
        super();
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        System.out.println("Service1 完了 残りカウント:" + latch.getCount());
    }
}

import java.util.concurrent.CountDownLatch;

public class Service2 implements Runnable {
    CountDownLatch latch;

    public Service2(CountDownLatch latch) {
        super();
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        System.out.println("Service2 完了 残りカウント:" + latch.getCount());
    }
}

このコードでは、各サービスが20秒の処理時間をシミュレートし、完了時に countDown() を呼び出します。メインスレッドは latch.await() で両方のサービスが完了するまで待機し、最後に「Done」を出力します。

使い分けのポイント

  • 固定スレッドプール: 同時実行数を厳密に制御したい場合や、サーバーアプリケーションのようにリソース消費を抑えたい場合に有効です。過剰なスレッド生成によるメモリ不足やCPU競合を防げます。
  • キャッシュスレッドプール: 短時間で完了する軽量タスクが不定期かつ大量に発生する場合に有効です。ただし、スレッド数が理論上無制限に増えるため、タスクの投入ペースが処理能力を大きく上回るとリソース枯渇のリスクがあります。

用途に応じて適切なスレッドプールを選択することが、安定性とパフォーマンスの両立につながります。

  1. JavaにおけるThreadとRunnableの違いを徹底解説!使い分けのポイント

    Javaで新しいスレッドを作成する方法は主に2つあります。1つ目はThreadクラスを継承(extends)する方法、2つ目はRunnableインターフェースを実装(implements)する方法です。それぞれの特徴と違いを理解することで、状況に応じた適切な選択ができるようになります。 スレッド作成の2つの方法 1. Threadクラスを継承する方法 Threadクラスのサブクラスとして新しいクラスを宣言し、そのサブクラス内でrun()メソッドをオーバーライドします。その後、サブクラスのインスタンスを生成し、start()メソッドを呼び出すことでスレッドが実行されます。 2. Runna

  2. Javaにおける例外(Exception)とエラー(Error)の違いを徹底解説

    Javaにおいて、例外(Exception)とエラー(Error)はどちらも Throwable クラスのサブクラスです。しかし、両者には明確な違いがあり、堅牢なアプリケーションを開発するうえで、それぞれの特性を正しく理解することは非常に重要です。 エラー(Error)とは エラーは、主にシステムリソースの不足によって発生する深刻な問題を示します。この種の問題はアプリケーション側でキャッチ(捕捉)すべきものではなく、基本的にプログラマーが対処できる範囲を超えています。代表的な例としては、システムクラッシュやメモリ不足(OutOfMemoryError)などが挙げられます。 エラーはほとんどの