在 Java 并发编程的历史上,「一个请求一个线程」的模型统治了二十多年。它简单、直观,却有着一个致命的天花板:平台线程(Platform Thread)是昂贵的操作系统资源。当我们的服务需要同时处理数万甚至数十万个 I/O 密集型请求时,线程数量成为了最先被耗尽的资源。Java 21 正式发布的虚拟线程(Virtual Threads),正是为了打破这个天花板。

一、平台线程的瓶颈在哪里

传统的 Java 线程是对操作系统线程的一对一封装。每个线程默认占用约 1MB 的栈空间,创建和上下文切换都需要陷入内核态。对于一个典型的 Web 服务:

过去我们用响应式编程(WebFlux、RxJava)来绕过这个问题,但代价是陡峭的学习曲线、割裂的代码风格和难以调试的回调链路。虚拟线程的思路则完全不同:不改写法,改运行时。

二、虚拟线程是什么

虚拟线程是由 JVM 调度、而非操作系统调度的轻量级线程。成千上万个虚拟线程被复用在少量平台线程(载体线程,Carrier Thread)上执行。当虚拟线程遇到阻塞 I/O 时,JVM 会将它从载体线程上「卸载」(unmount),让载体线程去执行其他虚拟线程。

一句话总结:虚拟线程让你用写同步代码的方式,获得接近异步编程的吞吐量。

创建一个虚拟线程非常简单:

// 方式一:直接启动一个虚拟线程
Thread vt = Thread.startVirtualThread(() -> {
    System.out.println("运行在虚拟线程中");
});

// 方式二:使用 Builder 定制
Thread.ofVirtual()
      .name("order-worker-", 0)
      .start(() -> handleOrder());

// 方式三:每个任务一个虚拟线程的 ExecutorService
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> queryDatabase());
}

三、Spring Boot 中的接入

Spring Boot 3.2 及以上版本对虚拟线程提供了一等支持,只需一行配置:

# application.yml
spring:
  threads:
    virtual:
      enabled: true

开启后,Tomcat 的每个请求都会由一个虚拟线程来处理,业务代码零改动。这正是虚拟线程最迷人的地方——它以极低的迁移成本,盘活了海量的存量同步代码。

四、实测数据对比

我在本地对一个「查询数据库 + 调用外部接口」的典型 I/O 密集型接口进行了压测(模拟 I/O 延迟 100ms,并发 5000):

需要注意的是,虚拟线程并非银弹。以下场景它帮不上忙:

  1. CPU 密集型任务:计算本身不阻塞,虚拟线程没有发挥空间;
  2. synchronized 内的阻塞调用:会导致「钉住」(Pinning)载体线程,建议改用 ReentrantLock;
  3. 依赖 ThreadLocal 大量传递大对象:百万级虚拟线程下内存开销不可忽视。

五、写在最后

虚拟线程是 Java 平台近十年来最重要的并发革新。它没有要求开发者学习新的编程范式,而是把复杂度下沉到了 JVM 内部。对于以 I/O 阻塞为主的企业级应用,升级 JDK 21 并开启虚拟线程,可能是 2026 年性价比最高的一次性能优化。

后续我还会继续分享虚拟线程在结构化并发(Structured Concurrency)中的实践,欢迎关注。