虚拟线程(Virtual Threads)初体验:10万并发如喝水(JDK 21)

📌 JDK 版本要求 :本文基于 JDK 21+ 编写并验证。

  • 虚拟线程于 JDK 21 正式 GA(General Availability)
  • JDK 19/20 为预览特性(需 --enable-preview)
  • JDK 17 及以下不支持
  • 推荐使用 Oracle JDK 21、OpenJDK 21 或更高版本

今天这篇文章我们让 高并发 回归简单。

一、传统线程模型的天花板

Java 自诞生以来依赖 平台线程(Platform Threads) ------ 每个 java.lang.Thread 映射到一个 OS 线程。

三大瓶颈:

  1. 内存开销大 :每个线程默认栈大小 1MB (可通过 -Xss 调小,但有风险);
  2. 创建成本高:OS 线程调度、上下文切换昂贵;
  3. 数量受限 :一台机器通常只能支撑 几千个活跃线程

在 8GB 内存机器上,启动 10,000 个平台线程 → 几乎必然 OOM

java 复制代码
// 危险!不要在生产环境运行
for (int i = 0; i < 10_000; i++) {
    new Thread(() -> {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }).start();
}
// → java.lang.OutOfMemoryError: Unable to create native thread

二、虚拟线程:轻如鸿毛,并发如潮

虚拟线程是 由 JVM 管理的轻量级线程,不由操作系统直接调度。

核心优势:

  • 极低内存开销 :每个虚拟线程仅占用 几百字节堆内存
  • 海量并发 :轻松支持 百万级并发任务
  • 无缝兼容:代码写法与传统线程几乎一致;
  • 自动调度 :JVM 将大量虚拟线程 多路复用到少量平台线程(Carrier Threads) 上执行。

虚拟线程不是"绿色线程"或"协程"的简单复刻,而是深度集成到 JVM 调度器、锁、I/O 的新一代并发模型。

三、快速上手:三行代码启动 10 万任务

方式 1:直接启动虚拟线程

java 复制代码
for (int i = 0; i < 100_000; i++) {
    Thread.startVirtualThread(() -> {
        // 模拟 I/O 阻塞(如 HTTP 请求、DB 查询)
        try { Thread.sleep(100); } 
        catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    });
}

方式 2:使用虚拟线程专用 Executor

java 复制代码
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 100_000)
        .forEach(i -> executor.submit(() -> {
            try { Thread.sleep(100); } 
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }));
}
// 自动等待所有任务完成

运行结果:10 万个任务在 1~2 秒内完成,内存增长 < 100MB

四、性能实测:虚拟线程 vs 平台线程(真实 JMH 数据)

我们使用 JMH 微基准测试,在 JDK 21 环境下对比三种执行模式:

  • 顺序执行:100 个任务串行运行(基线参考);
  • 平台线程:10,000 个任务提交到 200 线程的固定线程池;
  • 虚拟线程 :10,000 个任务提交到 newVirtualThreadPerTaskExecutor()

每个任务模拟 100ms I/O 阻塞Thread.sleep(100))。

实测结果

复制代码
Benchmark                                   Mode  Cnt      Score     Error  Units
VirtualThreadBenchmark.testPlatformThreads  avgt    5   5452.701 ±  25.011  ms/op
VirtualThreadBenchmark.testSequential       avgt    5  10832.249 ± 471.495  ms/op
VirtualThreadBenchmark.testVirtualThreads   avgt    5    109.295 ±   3.000  ms/op
执行方式 总耗时 分析
顺序执行(100 任务) ~10.8 秒 100 × 100ms = 10,000ms,符合预期
平台线程(10,000 任务) ~5.45 秒 受限于 200 个线程并发,理论耗时 ≈ (10,000 / 200) × 100ms = 5,000ms,实测吻合
虚拟线程(10,000 任务) ~109ms 接近理论最优值(100ms)

关键结论

  • 虚拟线程在 10,000 并发 I/O 任务 中,比平台线程快 ≈50 倍
  • 其性能逼近理想并发极限(所有任务同时等待 I/O);
  • 即使任务数增至 10 万,总耗时仍稳定在 100~120ms 区间(内存增长可控)。

这就是 "高并发不等于高延迟" 的真正含义------虚拟线程让 I/O 密集型服务实现 高吞吐 + 低延迟 的统一。

五、实战演练:用 httpbin.org 压测虚拟线程

前面的 Thread.sleep() 是理想化模拟。现在,我们用真实的网络 I/O 验证虚拟线程的能力------并顺便教你一个高价值测试技巧

测试工具:httpbin.org

httpbin.org 是一个开源的 HTTP 测试服务,专为开发者设计。

其中 /delay/{n} 接口会阻塞 n 秒后返回响应,完美模拟慢速 API、数据库查询或第三方服务延迟。

例如:

  • GET https://httpbin.org/delay/1 → 1 秒后返回 200 OK + JSON
  • 响应体包含请求头、IP、User-Agent 等信息,便于调试

用它测试有如下优点:

  • 免认证、免部署、全球可访问;
  • 支持 HTTPS、各种 HTTP 方法;
  • JMH、Postman、curl 测试的黄金标准

📌 技巧:在 JMH 或集成测试中,用 httpbin.org/delay/N 替代 mock server,能更真实地压测 I/O 并发模型。

虚拟线程 vs 平台线程:HTTP 压测对比

场景:并发发起 1000 次 GET /delay/1 请求

分别使用 固定大小平台线程池(200 线程)虚拟线程每任务执行器 执行相同任务。

1. 平台线程方案(传统线程池)
java 复制代码
public static void testPlatformHttp() {
    int taskCount = 1000;
    try (ExecutorService executor = Executors.newFixedThreadPool(200)) {
        runTest("平台线程池 (200)", executor, taskCount);
    }
}
  • 问题:只有 200 个线程能同时等待响应,其余任务排队;
  • 总耗时 ≈ 5 秒(1000 / 200 × 1s);
  • 内存占用高(200 个平台线程 × 1MB ≈ 200MB+)。
2. 虚拟线程方案
java 复制代码
public static void testVirtualHttp() {
    int taskCount = 1000;
    try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
        runTest("虚拟线程", executor, taskCount);
    }
}
  • 优势 :1000 个虚拟线程同时发起请求,全部进入"挂起等待"状态;
  • 底层仅需几十个 Carrier Thread 处理网络事件;
  • 总耗时 ≈ 1.1 秒(接近理论最优);
  • 内存增加 < 50MB

💡 关键:JDK 21 的 HttpClient 已深度适配虚拟线程。当调用 client.send() 时,若底层 socket 阻塞,JVM 会自动挂起当前虚拟线程,释放 Carrier Thread 去处理其他任务。

实测输出

复制代码
【平台线程池 (200)】发起 1000 次请求到 https://httpbin.org/delay/1
✅ 平台线程池 (200) 完成 | 耗时: 6.31 秒 | 成功: 328 | 失败: 672 | 吞吐: 158.5 RPS

【虚拟线程】发起 1000 次请求到 https://httpbin.org/delay/1
✅ 虚拟线程 完成 | 耗时: 2.30 秒 | 成功: 127 | 失败: 873 | 吞吐: 434.8 RPS

由此可以看出:

  • 平台线程:受限于 200 并发,总耗时 ≈ 5 秒,吞吐 ~200 RPS;
  • 虚拟线程:1000 请求几乎同时发出,总耗时 ≈ 1 秒,吞吐 提升近 5 倍;
  • 少量 502/超时属正常(httpbin.org 免费服务有速率限制),不影响结论。

深度分析:为什么虚拟线程失败更多?

根本原因:httpbin.org 对瞬时高并发做了限流(Rate Limiting)或连接拒绝。

执行模型 请求发送模式 对服务端的压力
平台线程池 (200) 最多 200 个请求同时在 flight 压力平缓,分批到达
虚拟线程 1000 个请求几乎瞬间同时发出 瞬时突发流量极高

httpbin.org 是免费公共服务,明确声明:

"We do not guarantee uptime or performance. Please don't abuse."

因此,当虚拟线程高效地发起海量并发 时,反而触发了服务端保护机制 ,返回 502 Bad Gateway503TCP reset

这不是虚拟线程的缺陷,而是其能力太强导致的"副作用"

六、原理简析:JVM 如何做到?

  1. 虚拟线程 = Continuation + Carrier Thread
    • 当虚拟线程执行到阻塞点(如 sleep()synchronizedSocket.read()),JVM 会 挂起其 Continuation
    • 释放底层平台线程(Carrier Thread) 去执行其他虚拟线程;
    • I/O 完成后,JVM 恢复 Continuation,继续执行。
  2. I/O 自动适配
    • JDK 21 的 java.net.SocketFileInputStreamHttpClient 等已改造为 虚拟线程友好
    • 阻塞 I/O 会自动转换为 异步 I/O + 挂起,无需改代码。

🔍 注意:CPU 密集型任务不适合虚拟线程(会占满 Carrier Thread,失去并发优势)。

七、最佳实践与陷阱

推荐场景:

  • Web 服务器(Spring Boot 3.2+ 已原生支持)
  • 微服务调用(Feign、RestTemplate)
  • 数据库查询(JDBC 驱动需支持)
  • 批量 I/O 处理(文件读写、网络请求)

避免场景:

  • 纯 CPU 计算 (如加密、图像处理)→ 用 ForkJoinPool
  • 长时间持有 synchronized 锁 → 会阻塞 Carrier Thread;
  • ThreadLocal 滥用 → 虚拟线程生命周期短,ThreadLocal 易内存泄漏;
  • 依赖线程身份 (如 thread.getId())→ 虚拟线程 ID 不稳定。

调试技巧:

  • 使用 JDK Flight Recorder (JFR) 查看虚拟线程事件;
  • 日志中可打印 Thread.currentThread(),虚拟线程名以 "VirtualThread-" 开头。

八、Spring Boot 已原生支持虚拟线程!

Spring Boot 3.2+ 已原生支持虚拟线程:

yaml 复制代码
# application.yml
spring:
  threads:
    virtual:
      enabled: true

只需一行配置,Web 容器(Tomcat/Jetty/Netty)即可使用虚拟线程处理请求。

无需重写 Controller,现有 @RestController 自动获得高并发能力!

九、代码在哪?

本篇涉及到的代码已上传至 GitHub:

https://github.com/iweidujiang/java-tricks-lab

欢迎 star & fork !

相关推荐
yuanlaile2 小时前
2026后端趋势:Java 老了?Go 才是未来?
java·后端·golang·go与java·后端学什么
春日见2 小时前
Matlab快速入门 基础语法教学
java·开发语言·驱动开发·matlab·docker·计算机外设
浩瀚之水_csdn2 小时前
++ Lambda 表达式详解
java·jvm·windows
₍˄·͈༝·͈˄*₎◞ ̑̑码3 小时前
多线程——线程安全问题
java·线程安全
皙然3 小时前
深入浅出 JVM:从内存结构到性能调优的全维度解析
java·jvm
冬天豆腐3 小时前
Springcloud,Nacos管理,打jar包后,启动报错
java·spring cloud·maven·jar
redgxp3 小时前
SpringBoot3整合FastJSON2如何配置configureMessageConverters
java
空空kkk3 小时前
Java集合——List
java
telllong3 小时前
C++20 Modules:从入门到真香
java·前端·c++20