仮想スレッド

概要

仮想かそうスレッド(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 ThreadVirtual 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();
}

参考リソース