Java 21+の仮想スレッド:適用シーン、スケジューリングの制約、移行方法
基準:Java 21 LTS。Java 24/25の仮想スレッド実装と構造化並行APIは、対象のJDKに合わせて再確認する必要があります。
1. どんな場面に向いているか
仮想スレッドは、ネットワーク、ファイル、データベースのI/Oで多数がブロックするタスクに適しています。ブロックすると仮想スレッドはアンマウントされ、プラットフォームスレッドは他のタスクを処理し続けられます。
CPU密集型タスクに割り当てられる計算資源が増えるわけではありません。データベースの接続プール、リモートサービス、スレッドセーフなコレクションの容量が増えるわけでもありません。
2. 最小限の使い方
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Response> future = executor.submit(() -> client.fetch(request));
Response response = future.get();
}
仮想スレッドで下流への呼び出しを際限なく連鎖させないでください。Semaphore、接続プール、リクエスト予算で同時実行数を制限します。
3. 移行時のチェック
ThreadLocalに大きめのオブジェクトを保存していないか、ブロッキング呼び出しが実際にアンマウント可能か、synchronizedのクリティカルセクションやロック競合、JNIまたはネイティブコードによるブロックがないか、キャンセルやタイムアウトが伝播するかを確認します。
JDK 21当時から使っていたpinningの診断は、引き続き残す価値があります。対象のJDKへアップグレードしたら、対応するJEPとリリースノートを読み直し、特定のバージョンの内部実装をAPIの約束として扱わないようにしてください。
4. 非同期APIとの比較
仮想スレッドを使えば、同期的なブロッキングコードを比較的低いスレッドコストで実行できます。非同期APIは、明示的なcontinuationまたはイベントループによってブロッキングを避けます。選択は、スレッド数だけでなく、チーム全体のモデル、ライブラリの互換性、デバッグ方法、下流のキャパシティによって判断してください。
5. 負荷テストの方法
同じ接続プール、タイムアウト、負荷条件、下流サービスを使い、プラットフォームスレッド、仮想スレッド、非同期実装のそれぞれについて、スループット、レイテンシ、メモリ、キャンセル、障害復旧をテストしてください。CPU密集型ベンチマークの結果からI/Oについて結論を導き出さないでください。