一文搞懂 Java 虚拟线程:让阻塞不再昂贵

举报
yd_236430038 发表于 2026/10/20 21:06:10 2026/10/20
【摘要】 JDK 21 正式发布虚拟线程(Virtual Threads,JEP 444),标志着 Java 并发模型进入新阶段。本文带你从动机、用法、原理到避坑,一次讲透。 一、为什么需要虚拟线程?回顾 Java 并发的历史,我们会发现一条清晰的主线:Thread-per-request(每请求一线程) → 线程池 → 异步响应式 → 虚拟线程。传统的平台线程(Platform Thread)是 1...

JDK 21 正式发布虚拟线程(Virtual Threads,JEP 444),标志着 Java 并发模型进入新阶段。本文带你从动机、用法、原理到避坑,一次讲透。

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

回顾 Java 并发的历史,我们会发现一条清晰的主线:Thread-per-request(每请求一线程) → 线程池 → 异步响应式 → 虚拟线程。

传统的平台线程(Platform Thread)是 1:1 映射到操作系统线程 的。这意味着:

  • 每个线程默认占用约 1MB 栈内存;
  • 线程上下文切换是内核态操作,成本高;
  • 一旦线程阻塞在 IO 上(数据库、HTTP、Redis),这个昂贵的资源就白白闲置。

一个典型的 Web 服务,单个请求可能要经历「查数据库 → 调下游接口 → 写缓存」三次阻塞。线程池只有 200 个线程,QPS 稍高就会排队,而 CPU 利用率却不到 20%。

虚拟线程的答案很直接:把「阻塞」的成本降到几乎为零。

二、虚拟线程是什么?

虚拟线程是 由 JVM 调度、运行在少量载体线程(Carrier Thread)之上的轻量级线程,是一种 M:N 调度模型:

百万级虚拟线程  ──M:N──>  少量平台线程(载体)
  • 创建成本:KB 级,百万级创建无压力;
  • 阻塞时:JVM 自动把它从载体线程上「卸载」,让载体去跑别的任务;
  • 恢复时:再「挂载」回某个载体继续执行。

关键点:虚拟线程没有变快,是变多了。它不是提升单线程吞吐,而是让「一请求一线程」这种简单模型重新变得可行。

三、快速上手

创建虚拟线程有三种方式:

// 1. 直接创建并启动
Thread.startVirtualThread(() -> {
    System.out.println("Hello from virtual thread!");
});

// 2. Builder 模式,可设置名称、异常处理
Thread.ofVirtual()
      .name("worker-", 0)
      .start(() -> doWork());

// 3. 并发场景推荐:每任务一虚拟线程的 Executor
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 100_000).forEach(i ->
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1)); // 阻塞?没关系
            return i;
        })
    );
} // close() 会等待所有任务完成

注意第 3 种写法:10 万个任务各自 sleep 1 秒,传统线程池根本不敢这么写,虚拟线程下轻描淡写。

实战:并发调用 100 个下游接口

旧写法(线程池 + 固定大小):

var executor = Executors.newFixedThreadPool(200);
List<Future<String>> futures = urls.stream()
        .map(url -> executor.submit(() -> httpClient.get(url)))
        .toList();

虚拟线程写法:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<String>> futures = urls.stream()
            .map(url -> executor.submit(() -> httpClient.get(url)))
            .toList();

    List<String> results = futures.stream()
            .map(this::safeGet)
            .toList();
}

业务代码保持了同步的直观写法,却拿到了接近异步框架的并发能力。这就是虚拟线程最大的价值:性能与可读性兼得。

四、原理浅探:一个任务如何「暂停又继续」

虚拟线程的实现核心在 JVM 内部,主要由三部分组成:

  1. Continuation(延续体):虚拟线程的执行栈被封装成 Continuation,支持「把当前栈冻结、稍后从断点恢复」;
  2. 调度器:默认是 ForkJoinPool 的 work-stealing 池,CPU 核数个载体线程;
  3. 阻塞点拦截:当虚拟线程执行阻塞操作(Thread.sleep、Socket IO、ReentrantLock.lock 等),JDK 把它替换为「挂起 Continuation」,栈数据从载体线程堆栈搬回堆内存。

简单说:阻塞虚拟线程时,真正的平台线程并没有被阻塞,它立刻可以去执行下一个就绪的虚拟线程。

五、避坑指南(重点)

坑 1:synchronized 导致 Pinning(钉死)

虚拟线程在 synchronized 块内发生阻塞时,无法被卸载,会一直占着载体线程,这就是「Pinning」。极端情况下,几个载体线程全被钉死,整个应用吞吐暴跌。

// 反例:synchronized 内做阻塞 IO
synchronized (lock) {
    return httpClient.get(url); // 阻塞期间 carrier 被钉住
}

// 正解:改用 ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    return httpClient.get(url);
} finally {
    lock.unlock();
}

JDK 24(JEP 491)已经改进了 synchronized 的 pinning 问题,但生产环境若还在 21/22,请务必注意。

排查手段:加 JVM 参数 -Djdk.tracePinnedThreads=full,或用 JFR 事件的 jdk.VirtualThreadPinned。

坑 2:不要池化虚拟线程

线程池存在的意义是「复用昂贵资源」。虚拟线程本身很廉价,池化它不仅无意义,还会引入排队延迟。正确的限流姿势:

  • 用 Semaphore 控制并发数;
  • 或依赖下游资源池(连接池)本身作为限流器。
private final Semaphore permits = new Semaphore(100); // 最多 100 并发

permits.acquire();
try {
    callDownstream();
} finally {
    permits.release();
}

坑 3:ThreadLocal 的内存放大效应

百万级虚拟线程 × 每个线程一份 ThreadLocal 副本 = 内存隐患。重型对象(如缓存、可变配置)请改用共享实例或 Scoped Values(JEP 446,预览特性)。

坑 4:CPU 密集任务不适用

虚拟线程解决的是 阻塞,不是 计算。CPU 密集任务请老老实实用平台线程池(大小 ≈ CPU 核数),混在一起跑还会饿死其他虚拟线程。

六、和响应式框架怎么选?

维度 WebFlux/Reactor 虚拟线程
写法 链式、回调,心智负担高 同步直白
阻塞检测 天然非阻塞 阻塞即挂起
生态兼容 需要响应式驱动 现有同步代码基本零改动
适用场景 高并发流式处理 绝大多数 IO 密集型业务

我的建议:新项目如果团队心智成本敏感,虚拟线程是更平滑的路线;已经深度使用响应式的团队也不必推翻重来,两者可以共存——用虚拟线程承载同步旧代码,用 Reactor 处理流式场景。

七、总结

虚拟线程没有引入新概念 API,而是让 Java 回到了最自然的并发模型:写同步代码,获得异步的扩展性。

三条黄金法则送给大家:

  1. 每任务一虚拟线程,不要池化;
  2. 注意 synchronized pinning,优先 ReentrantLock;
  3. 限流交给 Semaphore 或资源池,CPU 任务用平台线程。

从 JDK 21 开始,别再问「线程池开多大」,而是问「这个请求为什么不能拥有自己的线程」。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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