Javaで非同期タスクを実行するための4つのスレッド起動方法

現代のマルチコアCPU環境では、シングルスレッドの最適化よりも並列処理を活用した方が効率的なケースが多い。Javaでは非同期タスクを実行するために、以下の4つのスレッド初期化方式が利用できる。

1. Threadクラスの継承(戻り値なし)

Threadを継承し、run()メソッドをオーバーライドする。

public class CalculationTask extends Thread {
    @Override
    public void run() {
        System.out.println("スレッドID: " + Thread.currentThread().getId());
        int result = 20 / 4;
        System.out.println("計算結果: " + result);
    }
}

実行方法:

public class Main {
    public static void main(String[] args) {
        System.out.println("メイン開始");
        new CalculationTask().start();
        System.out.println("メイン終了");
    }
}

2. Runnableインターフェースの実装(戻り値なし)

Runnableを実装し、run()メソッドに処理を記述。

public class DataProcessor implements Runnable {
    @Override
    public void run() {
        System.out.println("スレッド名: " + Thread.currentThread().getName());
        int value = 15 / 3;
        System.out.println("処理結果: " + value);
    }
}

実行方法:

new Thread(new DataProcessor()).start();

3. Callable + FutureTask(戻り値あり)

Callableは戻り値と例外処理をサポートする。

import java.util.concurrent.Callable;

public class ResultTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("スレッド: " + Thread.currentThread().getName());
        int outcome = 18 / 3;
        System.out.println("結果: " + outcome);
        return outcome;
    }
}

実行と結果取得:

FutureTask<Integer> future = new FutureTask<>(new ResultTask());
new Thread(future).start();
Integer result = future.get(); // ブロッキング呼び出し

4. スレッドプールの利用

リソース管理とパフォーマンス向上のため、スレッドプールが推奨される。

カスタムThreadPoolExecutor

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    5,                          // コアスレッド数
    50,                         // 最大スレッド数
    30L,                        // アイドル時間
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100), // キュー容量を明示的に指定
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

ビルトインファクトリメソッド

  • Executors.newFixedThreadPool(n):固定サイズのプール
  • Executors.newCachedThreadPool():必要に応じてスレッドを生成・再利用
  • Executors.newSingleThreadExecutor():単一スレッドで順次実行

タスクの投入

ExecutorService executor = Executors.newFixedThreadPool(4);

// 戻り値不要
executor.execute(new DataProcessor());

// 戻り値あり(Runnable + デフォルト値)
Future<Integer> f1 = executor.submit(new DataProcessor(), 100);

// 戻り値あり(Callable)
Future<Integer> f2 = executor.submit(new ResultTask());

// 結果取得
System.out.println("f1: " + f1.get());
System.out.println("f2: " + f2.get());

スレッド数とCPUコアの関係

CPUが「24コア32スレッド」であっても、100以上のスレッドを同時に起動可能である。これはOSのスケジューラがタイムスライス方式で論理プロセッサにスレッドを割り当てるためである。ただし、物理/論理コア数を超えるスレッドを生成すると、コンテキストスイッチのオーバーヘッドが増加し、かえって性能が低下する可能性がある。I/Oバウンドな処理では多くのスレッドが有効だが、CPUバウンドな処理ではコア数に近いスレッド数が最適となることが多い。

タグ: Java マルチスレッド 非同期処理 スレッドプール Callable

7月20日 17:18 投稿