導入
従来のJavaにおける非同期処理は、CompletableFutureやExecutorServiceを用いたものが主流でした。しかし、これらはタスク間の親子関係やライフサイクル管理が曖昧になりやすく、例外発生時のスレッドリークやスタックトレースの追跡が困難という課題がありました。Java 21で導入された StructuredTaskScope は、タスクを一つのまとまり(構造)として扱うことで、これらの課題を解消し、より直感的で安全な並行処理を可能にします。
基礎知識
StructuredTaskScope は「構造化並行処理」を実現するためのクラスです。これまでの並行処理は、タスクを投げっぱなし(Fire-and-forget)にする傾向がありましたが、構造化並行処理では「タスクの開始地点と終了地点を同期させる」という考え方を採用します。これにより、あるタスクが失敗した場合、関連する他のタスクも確実にキャンセルされるという、堅牢なエラーハンドリングが保証されます。また、仮想スレッド(Virtual Threads)と組み合わせることで、軽量かつスケーラブルな並行処理が実現可能です。
実装/解決策
StructuredTaskScopeを利用するには、主に ShutdownOnFailure を使用します。これは、サブタスクのいずれかが失敗した際に、他のタスクを即座にキャンセルするポリシーを提供します。実装手順は以下の通りです。
1. try-with-resources文でスコープを定義する。
2. fork()メソッドを使用してサブタスクを起動する。
3. join()で全タスクの終了を待機する。
4. throwIfFailed()で発生した例外をチェックする。
サンプルプログラム
以下のコードは、複数のタスクを並行で実行し、結果を安全に集約する例です。
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class StructuredTaskExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// try-with-resourcesを使用することで、スコープ終了時に確実にリソースを解放
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 複数のタスクを並行で実行(仮想スレッド上で動作)
Future
Future
// 全タスクの終了を待機し、いずれかが失敗していれば例外を投げる
scope.join();
scope.throwIfFailed();
// 結果の取得
System.out.println(“結果: ” + task1.get() + “, ” + task2.get());
} catch (ExecutionException e) {
// いずれかのタスクが失敗した場合のハンドリング
System.err.println(“タスク実行中にエラーが発生しました: ” + e.getCause());
}
}
}
応用・注意点
StructuredTaskScope を利用する際は、以下の点に注意してください。
・スコープ外への参照: 起動したタスクの結果は、必ず同じスコープ内で取得するようにしてください。スコープを抜けるとFutureは無効化されます。
・仮想スレッドの活用: この機能は仮想スレッドと組み合わせることで真価を発揮します。従来のプラットフォームスレッドで大量のタスクをフォークするとリソース枯渇を招くため、スレッドの設計には注意が必要です。
・プレビュー機能の確認: バージョンによってはプレビュー機能として扱われる場合があるため、コンパイル時および実行時に適切なフラグ(–enable-preview)が必要になるケースがあります。環境に合わせてJavaのバージョンを確認してください。

コメント