Dev Study
← Spring コース

53. 1リクエスト1スレッドと仮想スレッド

既定では、 の1本が1つのリクエストを最初から最後まで受け持ちます。の中で Thread.sleep を呼べば、そのごと止まります。作業スレッドの上限は で、既定は 200 です。1秒眠るだけのハンドラへ同時に 400 件送ると、8回とも全体で約 2.1〜2.2 秒かかり、数えた5回では毎回ちょうど 200 件が後回しになっていました。

は、 全体で共有するの上に JVM が載せて動かすスレッドです(Java 21 以降)。=true にすると受け持ちがこれに替わり、同じ 400 件が8回中5回は約 1.1〜1.2 秒で終わりました。Node 24 の await でも約 1.1 秒でした。

時計が1秒進むまで回り続けるハンドラでは、結果が逆でした。10 CPU の環境で、Node は 20 件で約 20 秒、仮想スレッドは 20 件で約2秒、既定のスレッドは 40 件でも約 1.2 秒です。これは優劣ではなく、待ち方をどこに置いたかの違いです。なお、回り続ける処理は決まった量の計算ではないので、計算の速さは比べていません。仮想スレッドでも8回中3回は約 2.1〜2.2 秒かかり、原因は確かめていません。

点線の付いた語はタップすると説明が出ます。用語集を開く →

同じ「1秒待つ」と「1秒回り続ける」を3つの置き方で(10 CPU の環境での実測)
Tomcat の既定1リクエストに OS のスレッド1本(上限 200)
仮想スレッド1リクエストに仮想スレッド1本。JVM 全体で共有するスレッドの上で動く
Node 24JavaScript を動かすスレッドは1本。await で手放す
1秒待つ処理を同時 400 件
約 2.1〜2.2 秒8回とも。200 件が後回し
約 1.1〜1.2 秒8回中5回。残り3回は約 2.1〜2.2 秒
約 1.1 秒3回とも
時計が1秒進むまで回り続ける処理を同時 20 件
約 1.1 秒2回とも。40 件でも約 1.2 秒
約 2 秒2回とも。40 件では約4秒
約 20 秒2回とも。1件ずつ順番に
  • ・回り続ける処理は「時計が1秒進むまでループする」だけで、決まった量の計算ではありません。計算をこなす速さの比較にはなりません
  • ・仮想スレッドでは、回り続ける処理が 10 件(この環境の CPU 数)ずつ進んだように見えました。1.5 秒を超えたのは 20 件中 10 件、40 件中 30 件です
  • ・仮想スレッドの約 2.1〜2.2 秒の回では、遅かったのは一部のリクエスト(57 件・14 件など)で、原因は確かめていません
  • ・設定の説明文には、仮想スレッドを有効にすると threads.max は効かないと書かれています

サンプルコード(フレームワーク環境が必要なため表示のみ)

// ThreadsApp.java(抜粋)— Spring Boot 4.1.1
@RestController
class SleepController {
    @GetMapping("/thread")
    String thread() {
        Thread t = Thread.currentThread();
        return "toString=" + t + " name=" + t.getName() + " virtual=" + t.isVirtual();
    }

    @GetMapping("/sleep")
    String sleep() throws InterruptedException {
        Thread.sleep(1000);                        // このリクエストを受け持つスレッドが1秒止まる
        Thread t = Thread.currentThread();
        return t.isVirtual() ? "virtual " + t.getName() : t.getName();
    }
}

// 既定で起動して GET /thread(スレッド番号は実行ごとに変わる)
// toString=Thread[#41,http-nio-8080-exec-1,5,main] name=http-nio-8080-exec-1 virtual=false
//
// --spring.threads.virtual.enabled=true で起動して GET /thread
// toString=VirtualThread[#46,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1 name=tomcat-handler-0 virtual=true
//
// /sleep へ同時 400 件(全体の完了時間。10 CPU の Docker、クライアントも同じマシン、ウォームアップなし)
//   既定(上限 200)          : 8回とも約 2.1〜2.2 秒(2088〜2209 ms)
//   仮想スレッド             : 8回中5回が約 1.1〜1.2 秒(1117〜1158 ms)、3回が約 2.1〜2.2 秒(2139〜2185 ms)

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(5本) →

学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。

公式ドキュメントで詳しく ↗