Threadクラスの継承やRunnableインタフェースの実装といった従来のアプローチでは、run()メソッドがvoidを返す仕様であるため、並列処理の結果を呼び出し元で直接受け取ることができません。計算結果やデータ取得処理を非同期で実行しつつ、その戻り値を主スレッドで利用したい場合、CallableおよびFuture(実運用ではFutureTaskを経由)を利用する方式が標準的です。
この仕組みでは、タスクの実行ロジックと結果の監視・管理が明確に分離されます。具体的な構築手順は以下の通りです。
Callable<V>インタフェースを実装するクラスを定義する。call()メソッドをオーバーライドし、処理結果を返す(チェック例外のスローも可能)。- 定義したクラスをインスタンス化し、
FutureTask<V>のコンストラクタに渡す。 - 生成した
FutureTaskオブジェクトをThreadコンストラクタに指定してスレッドを起動する。 - 処理完了後、
FutureTask#get()メソッドを用いて結果を取得する。
Callableインタフェースに指定するジェネリクス型Vは、call()メソッドの戻り値型を定義します。これにより、型安全に非同期タスクの結果を受け渡すことが可能です。主要な構成要素と役割は次の通りです。
| メソッド / コンストラクタ | 役割と仕様 |
|---|---|
| V call() throws Exception | バックグラウンドで実行する処理を記述。結果を返し、処理失敗時は例外をスロー可能。 |
| FutureTask(Callable<V> task) | 指定されたCallableタスクをラップし、結果の監視・取得を管理するインスタンスを生成。 |
| V get() throws InterruptedException, ExecutionException | タスクの計算終了まで呼び出し側をブロックし、完了後に結果を返す。 |
以下に、指定範囲の整数に対する二乗和を計算する具体的な実装例を示します。ジェネリクスには計算結果の型としてIntegerを指定しています。
タスク実装クラス:
import java.util.concurrent.Callable;
public class SquareSumTask implements Callable<Integer> {
private final int upperBound;
public SquareSumTask(int limit) {
this.upperBound = limit;
}
@Override
public Integer call() throws Exception {
int accumulator = 0;
for (int current = 1; current <= upperBound; current++) {
accumulator += current * current;
}
return accumulator;
}
}実行および結果取得用クラス:
import java.util.concurrent.FutureTask;
public class AsyncExecutionDemo {
public static void main(String[] args) {
// タスクの定義(1から20までの二乗和を計算)
SquareSumTask computationJob = new SquareSumTask(20);
// FutureTaskによる結果管理ラッパーの生成
FutureTask<Integer> resultHandler = new FutureTask<>(computationJob);
// ラッパーをスレッドに割り当てて実行開始
Thread worker = new Thread(resultHandler);
worker.start();
try {
// 計算が完了するまで待機し、結果を取得
Integer computedValue = resultHandler.get();
System.out.println("計算結果: " + computedValue);
} catch (Exception e) {
System.err.println("非同期処理の実行中にエラーが発生しました: " + e.getMessage());
}
}
}get()メソッドを呼び出したタイミングで、対象スレッドのcall()処理が終了していない場合、現在のスレッドは結果が返されるまで一時的に待機状態に入ります。これにより、非同期処理と同期処理の連携を簡潔に制御できます。