找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4177

积分

0

好友

573

主题
发表于 1 小时前 | 查看: 3| 回复: 0

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都已停止维护,其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的学习曲线极为陡峭。开发者需要理解 MonoFlux,掌握 mapflatMapswitchMapzipWith 等操作符,还要搞懂背压机制、调度器配置和复杂的错误处理。

光是学习这些概念可能就需要数周时间。更棘手的是代码可读性问题。响应式代码中层层嵌套的 flatMapswitchIfEmpty,足以让任何开发者头晕目眩。

而使用虚拟线程呢?你只需要编写普通的、易于理解的阻塞式代码。

生态割裂与性能神话的破灭

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 来替代 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官方通过Spring AI 2.0和Spring Cloud 2025.1这两记重拳,宣告了一个技术时代的转向:对于绝大多数应用场景而言,虚拟线程已经足够。 你准备好拥抱这个变化了吗?




上一篇:JetBrains Air 智能体开发环境发布:解读技术架构、生态困境与开发者替代方案
下一篇:从JDK 12到17:盘点5大颠覆性语法对比,Java代码竟能如此简洁
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-20 05:28 , Processed in 0.620995 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表