仮想スレッド
概要
仮想スレッド(Virtual Threads)は Project Loom で導入された軽量スレッド実装で、JDK 21 から正式に利用可能。
JVM は
java.lang.Thread クラスを通じて OS スレッドの抽象を提供する。各 Platform Thread は 1-to-1 で OS/Kernel Thread に対応する。スレッドタイプ
Platform Thread(プラットフォームスレッド)
- OS Thread によって実行される
- 各 Platform Thread は一つの OS Thread に対応
- 作成と切り替えコストが高い
Virtual Thread(仮想スレッド)
- Platform Thread によって実行される
- 複数の Virtual Thread が同じ Platform Thread を共有できる
- 全ての Virtual Thread の記憶管理は Heap 内で行われ、通常のオブジェクトと同じ
flowchart TB
subgraph VT[Virtual Threads]
V1[VT 1]
V2[VT 2]
V3[VT 3]
V4[VT 4]
end
subgraph PT[Platform Threads]
P1[PT 1]
P2[PT 2]
end
subgraph OS[OS Threads]
O1[OS Thread 1]
O2[OS Thread 2]
end
V1 & V2 --> P1
V3 & V4 --> P2
P1 --> O1
P2 --> O2
なぜ仮想スレッドが必要か?
従来モデルの問題
Thread-per-request モデルでは、各リクエストに一つの OS Thread が必要:
flowchart LR
PT[Platform Thread] ==> OS[Kernel Thread]
問題:
- OS Thread は Memory blocked/IO 待機時に不必要にロックされる
- システムリソースの浪費
- アプリケーションの並行能力が制限される
仮想スレッドのソリューション
仮想スレッドは実際の作業を実行していない時、stack chunk object として Java Heap 記憶領域に格納される。
Virtual Threads は実際の作業を実行する時のみ Platform Threads に対応する。OS Threads は実際の作業を実行する必要がある時まで Platform Threads に割り当てられない。
仮想スレッドの作成
Thread.ofVirtual() を使用
VirtualThreadBasic.java
Thread vThread = Thread.ofVirtual()
.name("virtual-thread-1")
.start(() -> {
System.out.println("Hello from virtual thread!");
});ExecutorService を使用
VirtualThreadExecutor.java
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Task running on virtual thread");
});
}性能比較
| 特性 | Platform Thread | Virtual Thread |
|---|---|---|
| 作成コスト | 高い(OS リソースが必要) | 低い(純粋な JVM オブジェクト) |
| メモリ使用量 | 約 1MB stack | 約数 KB |
| 切り替えコスト | 高い(OS context switch) | 低い(JVM 内部切り替え) |
| 最大数 | OS に制限される(通常数千) | 数百万まで可能 |
使用シーン
仮想スレッドが適切
- IO 集約型タスク:HTTP リクエスト、DB クエリ、ファイル操作
- 高並行サービス:Web サーバーで大量のリクエストを処理
- 長時間待機:外部サービスの応答待ち
仮想スレッドが不適切
- CPU 集約型タスク:数学計算、画像処理
- スレッドアフィニティが必要:一部の Native ライブラリ
- synchronized ブロック内の長時間操作
注意事項
仮想スレッドは synchronized ブロックの実行中や native メソッドの呼び出し時に Platform Thread に「pin」され、実行権を譲れなくなる。
synchronized の代わりに ReentrantLock を使用することを推奨。PreferReentrantLock.java
// 非推奨:virtual thread pinning を引き起こす可能性
synchronized (lock) {
// 長時間 IO 操作
}
// 推奨:ReentrantLock を使用
lock.lock();
try {
// 長時間 IO 操作
} finally {
lock.unlock();
}既存コードとの統合
仮想スレッドは既存の Thread API と完全に互換性があり、シームレスに統合できる:
MigrateToVirtual.java
// 旧コード
ExecutorService executor = Executors.newFixedThreadPool(100);
// 仮想スレッドへ移行
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();Structured Concurrency(構造化並行性)
JDK 21 は仮想スレッドと併せて使用する構造化並行性のプレビュー機能も導入:
StructuredConcurrency.java
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> task1 = scope.fork(() -> fetchData1());
Future<String> task2 = scope.fork(() -> fetchData2());
scope.join(); // 全タスク完了を待機
scope.throwIfFailed(); // 例外があればスロー
return task1.resultNow() + task2.resultNow();
}