1. 導入:なぜCallableが必要なのか?
Javaで並行処理を行う際、真っ先に思い浮かぶのはRunnableインターフェースかもしれません。しかし、Runnableには「戻り値を取得できない」「例外をスローできない」という大きな制約があります。
現場の開発では「バックグラウンドで重い計算を行い、その結果をメインスレッドで受け取りたい」というケースが非常に多いです。この課題をスマートに解決するために用意されたのがCallable
2. 基礎知識:Runnableとの違い
Callableは、java.util.concurrentパッケージに属するインターフェースです。
Runnableのrun()メソッドが「void型(戻り値なし)」であるのに対し、Callableのcall()メソッドは「ジェネリクス型V(任意の戻り値)」を返し、かつ「throws Exception」が宣言されています。
つまり、計算結果を返すことと、途中で発生した例外を適切に呼び出し元へ伝えることができるのが最大の特徴です。
3. 実装:Callableの活用ステップ
Callableを利用するには、主に以下の手順を踏みます。
1. Callableを実装したタスクを作成する。
2. ExecutorService(スレッドプール)にタスクを渡す。
3. 返却されたFuture
Futureは「将来的な結果のプレースホルダー」であり、get()メソッドを呼ぶことで、処理が完了するまでメインスレッドを待機させ、計算結果を受け取ることができます。
4. サンプルプログラム
以下のコードは、スレッドを生成して計算を行い、結果を回収する一連の流れです。
import java.util.concurrent.;
public class CallableExample {
public static void main(String[] args) {
// スレッドプールを作成(1つのスレッドで実行)
ExecutorService executor = Executors.newSingleThreadExecutor();
// Callableタスクの定義(ラムダ式を使用)
Callable<Integer> task = () -> {
System.out.println("別スレッドで計算中...");
Thread.sleep(2000); // 重い処理を想定
return 100 + 200; // 計算結果を返す
};
// タスクを送信し、Futureオブジェクトを受け取る
Future<Integer> future = executor.submit(task);
try {
// 計算が終わるまで待機し、結果を取得
Integer result = future.get();
System.out.println("計算結果: " + result);
} catch (InterruptedException | ExecutionException e) {
// スレッドの中断や計算中の例外をここでキャッチできる
e.printStackTrace();
} finally {
// 忘れずにシャットダウンする
executor.shutdown();
}
}
}
5. 応用・注意点:現場でハマらないために
・ブロッキングに注意:
future.get()を呼ぶと、計算が終わるまでメインスレッドが停止します。これを「ブロッキング」と呼びます。現代のJava開発では、CompletableFutureを使って「非同期で処理を繋げる(コールバック処理)」ことで、ブロッキングを避ける設計が推奨されます。
・最新技術との組み合わせ:
Java 21以降で導入されたVirtual Threads(仮想スレッド)を使用する場合、ExecutorServiceは驚くほど軽量になります。また、Structured Concurrency(構造化並行処理)を使うと、複数のCallableタスクを一つのスコープで管理でき、エラーハンドリングがより堅牢になります。
まずは、ExecutorServiceでCallableを動かす基本をマスターし、徐々にCompletableFutureなどの非同期処理へステップアップしていきましょう!

コメント