最近 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. 移除 Reactive Kafka Binder -
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。还有 map、flatMap、switchMap、zipWith。背压机制。调度器配置。错误处理。
光学这些概念,几周过去了。更要命的是可读性。响应式代码嵌套的 flatMap、switchIfEmpty,看着就头晕。
而虚拟线程?写普通的阻塞代码就行,一眼看懂。
生态割裂和性能神话
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 引入)替代 ThreadLocal。ScopedValue 在作用域结束后自动清。
坑 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
