Java 21和Spring Boot 3升级笔记(三)虚拟线程使用边界

发表于 2024-10-11 22:07 1294 字 7 min read

Spring AI学习笔记(四)工具调用和MCPSpring AI学习笔记(三)RAG从文档入库到回答Spring AI学习笔记(二)ChatClient从怎么调到怎么封装Spring AI学习笔记(一)它到底解决什么问题java新版本-java25学习笔记(四)用JFR和GC日志做一次体检java新版本-java25学习笔记(三)虚拟线程要和资源边界一起看java新版本-java25学习笔记(二)运行时基线先统一java新版本-java25学习笔记(一)LTS版本对比和学习路线主流AI Agent能力对比与工程选型我用Kiro做了个自己的工具站盘一盘虚拟线程我用Trae做了个AstrBot的AI角色扮演插件Python初学笔记(六)常用标准库先学这几个Python初学笔记(五)读写文件和处理异常Python初学笔记(四)函数让代码开始有结构Python初学笔记(三)条件、循环和推导式Python初学笔记(二)变量和基础类型比想象中重要Python初学笔记(一)先把环境和运行方式弄明白主流AI大模型能力对比Java 21和Spring Boot 3升级笔记(五)日志指标与可观测性Java 21和Spring Boot 3升级笔记(四)数据访问层适配Java 21和Spring Boot 3升级笔记(三)虚拟线程使用边界Java 21和Spring Boot 3升级笔记(二)Jakarta迁移要点Java 21和Spring Boot 3升级笔记(一)工程基线整理魔法値をどうやって我慢する?JPAのSpecification大改造!处理生僻字乱码:JPA框架对于Oracle的NVarchar2,NChar,NClob类型支持Redis Stream能不能当轻量消息队列用RocketMQ 5学习笔记:普通消息之外要看什么事件流不是换个消息队列这么简单Kubernetes学习笔记04:发布、排障和观测Kubernetes学习笔记03:配置、密钥和存储Kubernetes学习笔记02:Deployment、Service和IngressKubernetes学习笔记01:Pod和控制器mysql索引原理02--存储引擎索引的实现mysql索引原理01--索引的数据结构
この投稿は「日本語」では表示できません。元の投稿を表示しています。
  Java 21 里最吸引人的特性之一就是虚拟线程。以前写高并发接口,大家很容易想到线程池、异步回调、CompletableFuture、响应式编程。虚拟线程出来以后,很多阻塞式代码也有了新的选择。   但我不太赞成一升级 Java 21,就把所有线程池都换成虚拟线程。它很好,但不是所有场景都适合。...

前言

  Java 21 里最吸引人的特性之一就是虚拟线程。以前写高并发接口,大家很容易想到线程池、异步回调、CompletableFuture、响应式编程。虚拟线程出来以后,很多阻塞式代码也有了新的选择。

  但我不太赞成一升级 Java 21,就把所有线程池都换成虚拟线程。它很好,但不是所有场景都适合。

  这篇主要记录一下我对虚拟线程使用边界的理解,尤其是放在 Spring Boot 项目里时,哪些地方可以尝试,哪些地方要谨慎。

它解决的是什么问题

  传统平台线程比较重。一个请求如果在等待数据库、等待 HTTP 接口、等待 Redis,线程也会被占着。并发一高,线程数量、上下文切换和内存都会成为压力。

虚拟线程的目标是让“一个任务一个线程”变得更便宜。代码仍然可以写成同步阻塞的样子,但等待 IO 时不会一直占着昂贵的平台线程。

比如这种代码:

public OrderDetail getDetail(String orderId) {
    Order order = orderClient.getOrder(orderId);
    PayInfo payInfo = payClient.getPayInfo(orderId);
    return new OrderDetail(order, payInfo);
}

从代码阅读上看,它比一堆回调更直接。虚拟线程的价值就是让这种同步写法在 IO 密集场景下有更好的伸缩性。

Spring Boot 里怎么打开

  Spring Boot 3.2 开始对虚拟线程支持更直接。配置上可以开启:

spring:
  threads:
    virtual:
      enabled: true

开启以后,Web 请求处理等场景会使用虚拟线程。不过这不代表项目里所有问题都解决了。

虚拟线程只是执行模型变化,数据库连接池、HTTP 连接池、第三方接口限流都还是原来的资源。线程变便宜了,不代表下游资源无限。

IO 密集场景更适合

  我觉得虚拟线程最适合的是 IO 密集场景。

比如:

1.调用多个内部 HTTP 服务。 2.查询数据库后组装结果。 3.读取对象存储或文件服务。 4.等待第三方接口返回。

这些场景里,线程大部分时间在等。虚拟线程能让等待成本降低。

但如果是 CPU 密集计算,比如大量加密、压缩、复杂报表计算,虚拟线程不会让 CPU 变多。这时更应该控制并发,避免把机器打满。

数据库连接池不能忘

  很多人试虚拟线程时会遇到一个问题:接口线程好像不缺了,但数据库连接池先爆了。

这很正常。以前 200 个平台线程可能天然限制了并发请求数量,现在虚拟线程能轻松创建很多,但数据库连接池还是只有几十个连接。

比如 Hikari 连接池:

spring:
  datasource:
    hikari:
      maximum-pool-size: 30

如果请求量很大,大量虚拟线程会一起等连接。它们本身不贵,但数据库并不会因为线程便宜就能承受更多查询。

所以引入虚拟线程后,更要看连接池、慢 SQL、下游限流。不要只看应用线程数。

ThreadLocal 要谨慎

  老项目里经常用ThreadLocal保存用户信息、租户信息、traceId。

虚拟线程也支持ThreadLocal,但因为虚拟线程数量可能很多,如果滥用 ThreadLocal,内存和上下文管理会变得更复杂。

我的习惯是能显式传参就显式传参,必须放上下文的再放。比如日志 traceId 可以继续交给成熟的链路追踪方案,不要自己到处手写。

同时要注意线程池混用。如果某段代码从虚拟线程切到自定义平台线程池,上下文不一定自动传过去。

不要盲目替换业务线程池

  项目里常见一些自定义线程池,比如导入任务线程池、消息消费线程池、报表生成线程池。

这些线程池通常不只是为了创建线程,也是为了限流。比如批量导入最多同时跑 5 个任务,不是因为线程不够,而是因为数据库和业务资源只能承受这么多。

如果直接改成虚拟线程,可能会把限流边界打破。

所以我会区分两种情况:

1.只是为了处理大量 IO 等待,可以考虑虚拟线程。 2.本身承担限流职责,就要保留并发控制。

线程便宜不等于业务可以无限并发。

小结

  虚拟线程是 Java 21 很重要的能力,尤其适合 Spring Boot 里大量同步 IO 的业务代码。它让代码可以保持直观写法,同时改善线程资源成本。

  但它不是性能万能药。数据库连接池、HTTP 连接池、下游限流、CPU 使用率都要一起看。我的建议是先在 IO 密集、边界清楚的接口上试,再逐步扩大范围,不要一上来把所有执行模型都改掉。

気に入ったならばコメントを残してくださいね~

© 2019 - 2026 VincentHo @VincentHo
Powered by theme astro-koharu · Inspired by Shoka