Java 21 虚拟线程实战:别再无脑加线程池了

举报
yd_212140359 发表于 2026/10/20 21:03:17 2026/10/20
【摘要】 写在前面Java 21 是 LTS 版本,里面最重磅的特性非 虚拟线程(Virtual Threads,JEP 444) 莫属。它的目标只有一句话:让"每个请求一个线程"这种简单直接的编程模型,在 Java 里重新变得可行。很多同学升级了 JDK 21,但代码里还是老一套 ThreadPoolExecutor,虚拟线程等于白装。这篇文章带你把虚拟线程用对,而不是用废。 一、为什么需要虚拟线...

写在前面

Java 21 是 LTS 版本,里面最重磅的特性非 虚拟线程(Virtual Threads,JEP 444) 莫属。它的目标只有一句话:让"每个请求一个线程"这种简单直接的编程模型,在 Java 里重新变得可行。

很多同学升级了 JDK 21,但代码里还是老一套 ThreadPoolExecutor,虚拟线程等于白装。这篇文章带你把虚拟线程用对,而不是用废。

一、为什么需要虚拟线程?

传统 Java 服务端的核心模式是 线程池 + 阻塞 IO:

// 传统写法:平台线程池
ExecutorService pool = Executors.newFixedThreadPool(200);

问题在于:

  1. 平台线程 1:1 映射操作系统线程,创建成本约 1MB 栈内存 + 内核调度开销,所以线程数不敢开大;
  2. Tomcat/JDBC 这类同步阻塞代码,线程大部分时间在 等 IO,200 个线程可能 180 个都在干等;
  3. 想提升吞吐只能加大线程池 → 上下文切换爆炸 → 稳定性反而变差;
  4. 想省资源只能改异步(CompletableFuture / WebFlux),但回调地狱、调试困难、ThreadLocal 失效等问题接踵而来。

虚拟线程的思路是:代码继续写同步阻塞的样子,但阻塞时把线程"让"出去。

二、虚拟线程到底是个啥?

虚拟线程是 由 JVM 调度的用户态线程,底层跑在少量"载体线程"(Carrier Thread,默认等于 CPU 核数)之上:

虚拟线程 A ─┐
虚拟线程 B ─┤ 挂载/卸载(Mount/Unmount)   载体线程 1 (OS Thread)
虚拟线程 C ─┘ ─────────────────────►     载体线程 2 (OS Thread)
                                          ...

关键机制:当虚拟线程执行到阻塞操作(如 socket.read()、sleep())时,JVM 会把它从载体线程上 卸载(unmount),载体线程立刻去跑别的虚拟线程;IO 就绪后再重新挂载。

所以:

  • 创建成本极低(几百字节),开百万级虚拟线程是常规操作,不是事故;
  • 阻塞 IO 不再浪费 OS 线程;
  • 你可以放心地写 Thread.sleep()、InputStream.read() 这类同步代码。

三、上手:三行代码改造

1. 创建单个虚拟线程

// 方式一:直接 start
Thread.startVirtualThread(() -> {
    System.out.println("跑在虚拟线程上: " + Thread.currentThread());
});

// 方式二:Builder
Thread.ofVirtual()
     .name("worker-")
     .start(task);

2. 用虚拟线程替代线程池

// 每个任务一个虚拟线程,用完即弃
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (var order : orders) {
        executor.submit(() -> handle(order));
    }
}   // AutoCloseable,close() 会等待所有任务完成

注意:不要试图池化虚拟线程,它本身就是为"一次一个"设计的,池化反而引入无意义的排队。

3. Spring Boot 3.2+ 一行配置

spring.threads.virtual.enabled=true

Tomcat 的请求处理线程会换成虚拟线程,同步 MVC 代码不动,吞吐直接受益。

四、实战验证:一个 IO 密集接口

模拟一个"聚合下游 3 个慢服务"的接口,每个下游耗时 1 秒:

static String fetchUpstream(String url) throws Exception {
    Thread.sleep(1000); // 模拟 IO 阻塞
    return url + "-ok";
}

// 平台线程池版本 vs 虚拟线程版本
static List<String> aggregate(List<String> urls, Executor executor) 
        throws Exception {
    List<Future<String>> futures = new ArrayList<>();
    for (String url : urls) {
        futures.add(executor.submit(() -> fetchUpstream(url)));
    }
    List<String> result = new ArrayList<>();
    for (var f : futures) result.add(f.get());
    return result;
}

压测 10000 个并发请求:

模型 配置 结果
平台线程池 200 线程 大量请求排队,P99 超过 50s,线程 CPU 上下文切换飙升
虚拟线程 每请求一个 排队消失,P99 接近单请求耗时,CPU 占用反而更低

结论很直白:瓶颈在 IO 等待时,虚拟线程用"同步代码的写法"拿到了"异步的吞吐"。

五、踩坑清单(重要!)

虚拟线程不是银弹,以下几类场景反而会更差:

❌ 1. CPU 密集型任务

虚拟线程在 CPU 计算时 不会主动让出载体线程,100 万个虚拟线程算哈希,最终还是在 N 个核上排队。CPU 密集任务请继续用固定大小线程池。

❌ 2. synchronized 钉住(Pin)载体线程

JDK 21 中,虚拟线程在 synchronized 块内阻塞会导致载体线程被 钉住(pinning),无法卸载,吞吐急剧下降。解决办法:

// 旧代码
synchronized (lock) { doSlowIo(); }

// 改为 ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
lock.lock();
try { doSlowIo(); } finally { lock.unlock(); }

好消息:JDK 24(JEP 491)已经解决这个问题,synchronized 不再 pin 载体线程。如果你还在 21,请检查热点路径。

❌ 3. 池化虚拟线程

Executors.newFixedThreadPool(n) 配虚拟线程毫无意义,newThreadPerTaskExecutor 才是正确姿势。

⚠️ 4. ThreadLocal 慎用于海量虚拟线程

百万级虚拟线程各挂一份 ThreadLocal,内存可能扛不住。尤其是 ThreadLocal<SimpleDateFormat> 这种重量级对象,考虑改用 ScopedValue(预览)或直接无状态化。

⚠️ 5. 调试与监控习惯要变

线程 dump 里将出现海量 VirtualThread,排查问题时学会用 toString() 里的 virtual 标识和 JFR 事件(jdk.VirtualThreadStart 等)。

六、什么场景该用?一句话判断

任务大部分时间在等(等 IO、等锁外部的慢响应)→ 用虚拟线程;任务大部分时间在算 → 用有限线程池。

适合的典型场景:

  • Web 请求处理(同步 MVC / Tomcat / Jetty)
  • RPC / HTTP 下游聚合调用
  • 消息消费的并发处理
  • gRPC 服务端

不适合的场景:

  • 加解密、压缩、图像/视频处理
  • 密集循环计算
  • 需要严格限制对下游连接数的场景(此时该用信号量限流,而不是靠线程数限流)

七、总结

  1. 虚拟线程把"同步阻塞写法"和"高并发吞吐"解绑了——这是 Java 并发编程十年一遇的模型升级;
  2. 正确姿势是 每个任务一个虚拟线程,不要池化;
  3. 三大军规:CPU 密集别用、JDK 21 里 synchronized 慢 IO 别用、海量 ThreadLocal 慎用;
  4. Spring Boot 3.2 用户先打开 spring.threads.virtual.enabled=true 跑一轮压测,很可能白捡几倍吞吐。

升级 JDK 21 只是第一步,把并发模型真正切换过来,才算把这个 LTS 用回本。


需要的话我可以再帮你:

  • 换个选题(比如 Stream 性能陷阱、record 进阶用法、JVM GC 调优等)
  • 调整篇幅或风格(更口语化 / 更学术一些)
  • 起几个吸睛的标题备选
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。