盘一盘虚拟线程

发表于 2025-08-06 22:45 1634 字 9 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升级笔记(一)工程基线整理How Can We Tolerate Magic Values! Major Overhaul of 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--索引的数据结构
This post is not yet available in English. Showing the original.
  虚拟线程从 Java 21 正式可用以后,现在已经不算新鲜概念了。   很多文章会强调它能创建大量线程,能让同步阻塞代码也有不错的并发能力。这些都对,但我觉得落地时不能只盯着“能开多少线程”。   虚拟线程改变的是 Java 处理并发 IO 的成本模型,但它没有取消数据库连接池、HTTP 下游容量、锁竞争、CPU 瓶颈这些现实限制。...

前言

  虚拟线程从 Java 21 正式可用以后,现在已经不算新鲜概念了。

  很多文章会强调它能创建大量线程,能让同步阻塞代码也有不错的并发能力。这些都对,但我觉得落地时不能只盯着“能开多少线程”。

  虚拟线程改变的是 Java 处理并发 IO 的成本模型,但它没有取消数据库连接池、HTTP 下游容量、锁竞争、CPU 瓶颈这些现实限制。

它适合什么

  虚拟线程适合 IO 密集型任务。

比如一个接口里要查数据库、调下游 HTTP、读 Redis、写日志,大部分时间都在等待。这种场景下,传统平台线程被阻塞时成本比较高。虚拟线程阻塞时可以让出载体线程,系统能承载更多等待中的任务。

它尤其适合同步编程模型。以前为了高并发,可能要写异步回调或响应式代码,复杂度会上来。虚拟线程让我们可以继续写比较直观的同步代码。

但如果任务是 CPU 密集型,比如大规模加密、图片处理、复杂计算,虚拟线程帮不上太多。CPU 就那么多核,算不过来就是算不过来。

不要把连接池打爆

  虚拟线程让请求线程变便宜,但数据库连接没有变便宜。

如果一个接口每次都要查数据库,虚拟线程并发上来以后,数据库连接池可能先被打满。请求都在等连接,看起来线程很多,实际吞吐不一定提升。

所以启用虚拟线程后,更要关注连接池:

1.最大连接数。 2.等待连接耗时。 3.活跃连接数。 4.慢 SQL。 5.数据库 CPU 和锁等待。

不要以为线程不贵了,下游资源也无限了。虚拟线程只是把应用层等待成本降低,下游容量仍然是硬约束。

synchronized 和锁要检查

  虚拟线程最怕一些不经意的锁竞争。

如果大量虚拟线程在某个synchronized块上排队,吞吐照样会掉。更麻烦的是某些阻塞操作在特定情况下可能让载体线程被占住,影响调度效果。

日常业务代码里,很多锁是历史遗留的。比如缓存刷新、单例工具、编号生成、定时任务保护。这些平时没问题,并发上来后就可能变成瓶颈。

所以测试虚拟线程时,不只要看接口吞吐,还要看线程状态、锁竞争和 JFR 事件。

ThreadLocal 要重新审视

  Java Web 项目里 ThreadLocal 很常见。

用户上下文、租户上下文、traceId、数据源路由、登录信息,都可能放 ThreadLocal。虚拟线程下 ThreadLocal 仍然可用,但线程数量可能更多,使用方式要谨慎。

首先要保证请求结束后清理。否则上下文泄漏会更难排查。

其次,不要往 ThreadLocal 放大对象。虚拟线程数量多时,大对象会放大内存压力。

如果只是传递只读上下文,可以关注 Scoped Value 这类更适合结构化上下文传递的能力。不过在业务项目里,是否使用还要看 JDK 版本和团队熟悉程度。

线程池思维要调整

  传统平台线程里,我们经常用线程池控制并发。线程池大小本身就是一种限流。

虚拟线程不太适合用固定小线程池来复用。它的使用方式更像是每个任务一个虚拟线程。

但并发仍然要控制,只是控制点应该转移到资源边界上。比如数据库连接池、HTTP Client 连接池、信号量、限流器、队列长度。

如果某个下游最多承受 100 并发,就用限流或信号量控制,不要指望虚拟线程自己知道。

Spring 项目怎么试

  Spring Boot 3.x 对虚拟线程已经有比较方便的支持。

但我不建议全项目一键打开后直接上线。可以先选几个 IO 密集接口做压测对比。

观察指标包括:

1.接口吞吐。 2.P95 和 P99 延迟。 3.CPU 使用。 4.数据库连接等待。 5.GC 和内存。 6.下游错误率。 7.锁竞争。

如果只是吞吐提高但下游错误率也提高,那不是成功。说明并发压力被推给下游了。

日志和排查会有变化

  虚拟线程数量多以后,线程 dump 会比以前更热闹。

排查时不能简单看到很多线程就紧张。要看它们在等什么,是等数据库连接、等 HTTP、等锁,还是在执行 CPU 任务。

JFR 对虚拟线程排查很有帮助。它能看到线程调度、阻塞、锁和方法采样。相比直接看巨大线程栈,JFR 更容易找到重点。

日志里的线程名也可能变化。如果团队有依赖线程名判断业务的习惯,最好调整掉。线程名不是稳定业务语义。

批处理任务也要谨慎

  虚拟线程不只会出现在 Web 请求里,也可能被用到批处理任务。

比如一次批量同步十万条数据,如果每条数据都开一个虚拟线程去调下游,看起来代码很简单,但下游服务和数据库很可能扛不住。

批处理更需要控制节奏。可以把任务拆批,每批固定大小,再用信号量限制同时执行的数量。虚拟线程负责降低等待成本,批处理框架负责控制总体压力。

我不太建议把“每个任务一个虚拟线程”理解成“所有任务一起发出去”。工程里永远要问一句:下游能不能接住。

小结

  虚拟线程是 Java 并发模型里很重要的一步,尤其适合同步 IO 密集型后端服务。

  但它不是无限并发按钮。落地时要看数据库连接池、下游容量、锁竞争、ThreadLocal、内存和观测工具。

  我的建议是先用小范围压测验证,再按资源边界做限流。虚拟线程让线程便宜了,但系统里真正贵的东西仍然要认真管理。

If you enjoyed this, leave a comment~

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