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的发布日志揭示了两个关键变化:
- 移除 Reactive Kafka Binder
- Gateway新增对MVC + 虚拟线程的支持
第一个变化在意料之中。Reactor Kafka都已停止维护,其Binder被移除只是时间问题。
第二个变化才是真正的震动点。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
从配置上看几乎一模一样,但底层实现已完全不同。从社区的使用反馈来看,虚拟线程版本不仅性能表现优异,代码也更为简洁。
响应式在Spring生态的最后一个优势领域,也正被虚拟线程拿下。
三、WebFlux到底错哪儿了?
2017年WebFlux诞生时,平台线程还是“昂贵”的资源。C10K问题(一万并发连接)是每个高并发系统的噩梦。响应式编程给出了一个方案:用事件循环替代“每请求一线程”的传统模型。但这场技术革命是有代价的。
学习曲线:从入门到崩溃
WebFlux的学习曲线极为陡峭。开发者需要理解 Mono 和 Flux,掌握 map、flatMap、switchMap、zipWith 等操作符,还要搞懂背压机制、调度器配置和复杂的错误处理。
光是学习这些概念可能就需要数周时间。更棘手的是代码可读性问题。响应式代码中层层嵌套的 flatMap、switchIfEmpty,足以让任何开发者头晕目眩。
而使用虚拟线程呢?你只需要编写普通的、易于理解的阻塞式代码。
生态割裂与性能神话的破灭
WebFlux要求整个技术栈都是响应式的。这意味着数据库访问不能使用成熟的JDBC,而必须采用R2DBC。然而,R2DBC的生态远不如JDBC完善,驱动支持少、连接池方案不成熟、ORM支持弱。
此外,响应式编程的“性能神话”也在逐渐破灭。社区的实测数据显示,虚拟线程的性能在绝大多数场景下已几乎追平WebFlux,而代码复杂度却低了不止一个量级。
99%的场景,虚拟线程确实足够了。
四、虚拟线程的5大坑:从入门到放弃
虚拟线程并非万能银弹,如果不了解它的限制,很容易从“快速上手”直接跌入“果断放弃”的深坑。以下整理了几个实践中常见的陷阱,供大家参考。
坑1:Synchronized Pinning(最致命)
虚拟线程的核心机制是“挂起和恢复”。当它被阻塞时,会从底层的平台线程上卸载(Unmount),让平台线程去执行其他虚拟线程。
但有一个致命的例外:虚拟线程在 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核心数设置,大量虚拟线程会争抢少量连接,导致等待队列爆满,性能反而下降。
解决方案:适当增大连接池,建议设置在50-100之间。
spring:
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
但需要注意:连接池不能无限放大。 数据库连接本身是昂贵的资源,过大的连接池会给数据库服务器带来巨大压力。
坑3:ThreadLocal内存泄漏
平台线程通常在线程池中复用,因此 ThreadLocal 中存储的数据也能被复用。但虚拟线程更像是“一次性”的,用完后就会被销毁。
问题在于,ThreadLocal 中存储的数据并不会随虚拟线程的销毁而自动清理。如果创建了成千上万个虚拟线程,每个都持有一份 ThreadLocal 数据,内存可能会迅速耗尽。
解决方案:使用Java 21引入的 ScopedValue 来替代 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官方通过Spring AI 2.0和Spring Cloud 2025.1这两记重拳,宣告了一个技术时代的转向:对于绝大多数应用场景而言,虚拟线程已经足够。 你准备好拥抱这个变化了吗?