最近 Spring Cloud 2025.1.0 和 Spring AI 2.0.0 (预览版) 两个重量级版本相继发布。打开发布日志仔细看,你会发现一个共同点:虚拟线程(Virtual Threads)正在成为 Spring 生态的主流选择。

Spring AI 2.0 强制要求 Java 21。
Spring Cloud 2025.1 移除了 Reactive Kafka Binder,Gateway 新增对 MVC + 虚拟线程的支持。

Java 21 的核心特性是什么?虚拟线程。
响应式编程的核心卖点是什么?高并发、非阻塞。

Java 架构师 Brian Goetz 曾有一句著名论断:"虚拟线程将终结响应式编程。"

这一预言,正在 Spring 生态中逐渐变为现实。

这不是巧合。这是 Spring 官方在用行动投票:99% 的场景,虚拟线程够了。

更绝的在后面。就在 Spring Cloud 2025.1 发布时,Spring 官方扔出一条公告:Reactor Kafka 项目停止维护。

理由就一句话:"虚拟线程成熟了,响应式的核心动机没了。"

压倒响应式编程的最后一根稻草,落了。


一、Spring AI 2.0:为什么死磕 Java 21?

Spring AI 2.0 的发布日志里,有句话看着不起眼,但很耐人寻味:

"This major platform upgrade aligns Spring AI with the latest Spring ecosystem."

翻译过来:Spring AI 要跟上 Spring 生态的步伐,所以死磕 Java 21。

但为啥一定要 Java 21?答案藏在 AI 应用的并发特征里。

AI 应用的并发噩梦

复杂场景:AI Agent 的多步骤编排。

Spring AI 2.0 引入了 Agentic AI 框架(Spring AI Agents)。一个自主 AI Agent 执行任务时可能需要:

  • • 分析用户需求(调用 LLM,2-5 秒)
  • • 查询多个数据源(数据库、向量库、外部 API,每个 0.5-2 秒)
  • • 递归调用多个工具(Tool Calling,每个工具可能再调用 LLM)
  • • 评估结果质量(LLM-as-a-Judge,再调一次 LLM)
  • • 失败重试和任务回溯

单个 Agent 可能触发几十次 I/O 操作。

1000 个并发 Agent?传统线程模型直接崩溃。

WebFlux 能解决吗?能,但代价是代码复杂度暴涨。AI Agent 的递归调用、工具链编排、多智能体协同,用 WebFlux 写出来基本不可维护。

虚拟线程:让 AI 应用回归简单

Spring AI 2.0 基于 Java 21 的虚拟线程特性,对底层的 HTTP 客户端(如 RestClient)和连接池进行了优化。

虚拟线程在被阻塞时会自动挂起(Unmount),把底层的平台线程让给其他虚拟线程用。一个平台线程可以承载成千上万个虚拟线程。

写同步代码,享异步性能。这种"写同步代码,享异步性能"的特性,极大降低了构建高性能 AI 网关的门槛。

更关键的是,虚拟线程让复杂的 AI Agent 编排变得简单。

AI 应用的并发特征 + 虚拟线程的成熟 = Java 21 必选。


二、Spring Cloud 2025.1:响应式的"大撤退"

Spring Cloud 2025.1 的发布日志里,有两个变化:

  1. 1.  移除 Reactive Kafka Binder
  2. 2.  Gateway 新增对 MVC + 虚拟线程的支持

第一个变化在意料之中。Reactor Kafka 都停止维护了,移除只是时间问题。

第二个变化才是真地震。

Spring Cloud Gateway 是响应式编程在 Spring 生态里最成功的一个案例。它基于 WebFlux,就是为高并发网关场景打造的。

虚拟线程出现之前,网关确实是响应式的地盘。

现在,Gateway 出了个 MVC 版本。全面支持虚拟线程。

配置对比:

Gateway Reactive

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service

Gateway MVC + 虚拟线程

spring:
  cloud:
    gateway:
      mvc:
        routes:
          - id: user-service
            uri: lb://user-service
  threads:
    virtual:
      enabled: true

配置几乎一模一样。底层完全不同。

从社区的使用反馈来看,虚拟线程版本不仅性能更好,代码也更简单。

响应式最后一个优势领域,也被拿下了。

三、WebFlux 到底错哪儿了?

2017 年 WebFlux 诞生时,平台线程还是"贵"资源。C10K 问题(1 万并发连接)是每个高并发系统的噩梦。响应式编程给了个方案:事件循环替代每请求一线程。但革命是有代价的。

学习曲线:从入门到崩溃

WebFlux 的学习曲线陡得离谱。你得搞懂 Mono 和 Flux。还有 mapflatMapswitchMapzipWith。背压机制。调度器配置。错误处理。

光学这些概念,几周过去了。更要命的是可读性。响应式代码嵌套的 flatMapswitchIfEmpty,看着就头晕。

而虚拟线程?写普通的阻塞代码就行,一眼看懂。

生态割裂和性能神话

WebFlux 要求你用响应式驱动。数据库访问不能用 JDBC,得用 R2DBCR2DBC 的生态比 JDBC 差远了。驱动支持少、连接池方案不成熟、ORM 支持弱。

而且,响应式的性能神话也破了。社区的实测结果显示,虚拟线程的性能几乎追平 WebFlux,代码复杂度却低太多。

99% 的场景,虚拟线程够了。


四、虚拟线程的 5 大坑:从入门到放弃

虚拟线程不是银弹,不了解它的限制,很容易从"入门"直接跳到"放弃"。整理几个笔者在实践中踩过的坑,供大家参考。

坑 1:Synchronized Pinning(最致命)

虚拟线程的核心机制是"挂起和恢复"。被阻塞时,它会从底层的平台线程上卸载,让平台线程跑其他虚拟线程。

但有个致命的例外:虚拟线程在 synchronized 块里被阻塞,卸不下来。

这叫 Pinning(钉住)。

public class PigOrderService {
    private final Object lock = new Object();

    public Order createOrder(OrderRequest request) {
        synchronized (lock) {
            // 查询用户信息(阻塞操作)
            User user = userRepository.findById(request.getUserId());
            // 查询商品库存(阻塞操作)
            Product product = productRepository.findById(request.getProductId());
            // 虚拟线程在这里被钉住,卸不下来
        }
    }
}

虚拟线程在 synchronized 块里查数据库。查询时被阻塞,卸不下来。底层的平台线程也被阻塞。

好消息:Java 25 已经解决了这个问题。

坑 2:数据库连接池策略大逆转

平台线程时代,数据库连接池有个经典公式:

连接池大小 = CPU 核心数 × 2

虚拟线程时代,这公式失效了。

虚拟线程能创建成千上万个。如果还用 CPU 核心数 × 2 的连接池,大量虚拟线程抢少量连接,等待队列爆了。

性能反而掉。

解决:适当增大连接池,推荐 50-100。

spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10

但注意:连接池不能无限大。 数据库连接是贵资源。池太大,数据库扛不住。

坑 3:ThreadLocal 内存泄漏

平台线程在线程池里复用,ThreadLocal 的数据也复用。虚拟线程是"一次性"的。用完销毁。

但 ThreadLocal 的数据不会自动清。创建成千上万个虚拟线程,内存瞬间爆。

解决:用 ScopedValue(Java 21 引入)替代 ThreadLocalScopedValue 在作用域结束后自动清。

坑 4:@Async 无界并发

Spring 的 @Async 在虚拟线程时代有个隐藏的坑。

默认情况下,@Async 用 SimpleAsyncTaskExecutor。虚拟线程时代,它为每个任务创建一个新虚拟线程。

听着挺好?虚拟线程创建几乎没开销,但并发数可能失控。

坑 5:CPU 密集型任务性能下降

虚拟线程适合 I/O 密集型(数据库查询、网络请求、文件读写)。但 CPU 密集型(加密、压缩、图像处理),虚拟线程性能反而不如平台线程。

原因:虚拟线程调度有额外开销。对不阻塞的 CPU 密集型任务,这些开销纯属浪费。解决:任务隔离。不同线程池干不同的活。

@Configuration
public class PigThreadPoolConfig {

    // I/O 密集型:虚拟线程(订单查询、商品查询等)
    @Bean("ioExecutor")
    public ExecutorService ioExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }

    // CPU 密集型:平台线程池(图片压缩、报表计算等)
    @Bean("cpuExecutor")
    public ExecutorService cpuExecutor() {
        return Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors()
        );
    }
}

结尾

以前用 WebFlux,是"没得选"。线程贵,得省着用。非阻塞 I/O、事件循环、响应式流,这些复杂的技术是高并发的代价。

简单的阻塞代码,异步的性能。虚拟线程让 Java 开发者重新找回写代码的快乐。

Spring 官方这两记重拳,宣告了一个时代的终结:

99% 的场景,虚拟线程够了。

你准备好拥抱虚拟线程了吗?



来源:https://mp.weixin.qq.com/s/VHNPV6jc_F9NUNWtni98LA