1. 導入:なぜ複数の非同期処理が必要なのか
Javaで非同期処理を行う際、単一のタスクだけでなく「複数のAPIを同時に叩いて、その結果を待つ」といったシーンは非常に多いです。しかし、バラバラに非同期処理を実行すると、結果の待ち合わせや例外処理が複雑になりがちです。今回紹介する CompletableFuture.allOf() と anyOf() を使えば、こうした複雑なタスクの同期や管理をシンプルに解決できます。
2. 基礎知識:CompletableFutureとは
CompletableFuture は、Java 8で導入された非同期処理を扱うためのクラスです。単なる「スレッドの実行」にとどまらず、処理が完了した後のアクション(コールバック)や、複数の処理を組み合わせるパイプライン処理を柔軟に行えます。
今回扱うメソッドは、複数のCompletableFutureを1つにまとめるための「結合ツール」だと考えてください。
3. 実装と解決策
allOf() は「指定したすべての処理が完了するまで待機する」メソッドです。例えば、3つの外部サービスからデータを取得し、すべて揃った後に集計処理を行う場合に使用します。
一方、anyOf() は「指定した処理のうち、どれか1つでも完了したら先に進む」メソッドです。複数のサーバーにリクエストを送り、一番早く返ってきた結果を採用する(レースコンディション)ような場面で役立ちます。
4. サンプルプログラム
以下のコードは、allOfを使用した並列タスク実行の例です。そのままコピー&ペーストして実行可能です。
import java.util.concurrent.CompletableFuture;
public class AsyncExample {
public static void main(String[] args) {
// 1つ目の非同期タスク
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
sleep(1000); // 処理をシミュレート
return "データA";
});
// 2つ目の非同期タスク
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "データB";
});
// すべてのタスクが完了するまで待機する
CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2);
// 完了後に結果を取得して統合する
allTasks.thenRun(() -> {
String result1 = task1.join();
String result2 = task2.join();
System.out.println("結果: " + result1 + " と " + result2 + " が揃いました");
}).join(); // メインスレッドが即終了しないように待機
}
private static void sleep(int ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
5. 応用・注意点:現場で陥りやすい罠
現場で使う際に最も注意すべきは 例外処理 です。allOf() は、いずれか1つの処理が失敗しても、そのままではエラーを無視して進んでしまうことがあります。
また、Java 21以降で注目されている Virtual Threads を併用する場合、スレッド生成のコストは非常に低いため、CompletableFutureを過度に複雑にチェーンさせるよりも、シンプルな構造を心がけるのがベストです。さらに高度な制御が必要な場合は、Java 21の Structured Concurrency(構造化並行処理) の導入も検討してみてください。これらはスレッドの生存期間を明確に管理できるため、より安全で保守性の高いコードが書けます。

コメント