跳至主要內容

JDK21 到 JDK25,为什么基本没见 Java 圈对虚拟线程有什么热度,还在抱着老一套线程池?

程序员小富大约 7 分钟

JDK21 到 JDK25,为什么基本没见 Java 圈对虚拟线程有什么热度,还在抱着老一套线程池?

这个现象确实存在,但原因比"Java 程序员保守"要复杂得多。


先说一个残酷的事实:大部分 Java 项目还在用 JDK 8 或 11

虚拟线程在 JDK 21 才正式转正。而根据各种开发者调查报告,2025 年底 JDK 8 的使用率还在 30% 以上,JDK 11 和 17 加起来又占了一大块。真正在生产环境跑 JDK 21+ 的项目,占比可能也就 15%-20%。

也就是说,大部分 Java 开发者连用虚拟线程的前提条件都不具备。不是他们不想用,是项目升级 JDK 版本这件事本身就很难推动。

企业里升级 JDK 版本不是改个 pom.xml 配置就完事的。要评估所有依赖库的兼容性、要回归测试整个系统、要更新 CI/CD 的基础镜像、要重新评估 GC 参数和 JVM 调优配置、要走变更审批流程。很多团队的态度是"现在跑得好好的为什么要动它",升级 JDK 带来的收益不够明确,但风险很具体。

这是 Java 生态的老问题了。每次 JDK 出新特性,都要等三五年才在企业里真正铺开。Lambda 是 JDK 8 的特性,到 2018 年左右才成为主流写法。虚拟线程也一样,现在还处在"早期采用者"阶段。


虚拟线程解决的问题,大部分业务系统并不迫切

虚拟线程的核心价值是:让你可以用极低的成本创建大量线程来处理并发的 I/O 操作。传统平台线程一个就占 1MB 左右的栈空间,创建几千个就要几个 GB 内存。虚拟线程的开销是 KB 级别的,创建几十万个都没问题。

这在理论上很厉害,但你想想,你的业务系统真的需要同时处理几十万个并发请求吗?

大部分企业的 Java 后端应用,并发量在几百到几千的级别。Tomcat 默认 200 个线程,配合连接池和异步处理,够用了。就算扛不住了,横向加几台机器比改代码便宜多了。

虚拟线程真正发光的场景是高并发 I/O 密集型服务——比如网关、代理、消息中间件、大量外部 API 调用的聚合服务。这些场景确实存在,但不是每个团队都在做这种事情。大部分业务开发者的日常是写 CRUD、调接口、处理业务逻辑,线程池那一套完全够用。

你让一个写管理后台的开发者去研究虚拟线程,他的反应大概率是"这跟我有什么关系"。


换了虚拟线程,并不代表可以不用线程池了

这是一个很常见的误解。很多介绍虚拟线程的文章给人一种印象:有了虚拟线程,线程池就可以扔掉了,直接 Thread.startVirtualThread() 就完事。

不是的。

虚拟线程解决的是"线程太贵"的问题,但线程池解决的不只是"线程太贵"的问题。线程池还负责:限制并发度(防止一下子打满下游服务或数据库)、任务排队(削峰填谷)、拒绝策略(系统过载时的降级处理)、线程复用和生命周期管理。

如果你有一个数据库连接池最大连接数是 50,就算你用虚拟线程同时发起了一万个数据库查询,也只有 50 个能同时执行,其余的都在等连接。虚拟线程不会让你的数据库突然能扛一万个并发。

在很多场景下,用了虚拟线程你还是需要某种形式的并发控制,只不过可能从 ThreadPoolExecutor 变成了 Semaphore + 虚拟线程的组合。代码的复杂度不一定降低了多少。


生态适配是个大工程

虚拟线程要发挥效果,依赖一个关键前提:你的 I/O 操作不能 pin 住平台线程。

什么意思呢?虚拟线程在执行到 I/O 阻塞操作时,会自动从底层的平台线程上卸载,让平台线程去执行其他虚拟线程。但如果你的代码里用了 synchronized 关键字做同步,或者调用了 JNI 原生方法,虚拟线程就没法卸载,会一直占着平台线程不放。这就是所谓的"pin"。

一旦发生 pin,虚拟线程的优势就大打折扣,甚至可能比普通线程池还差,因为默认的 carrier 线程池很小(通常等于 CPU 核心数)。

问题是,你项目里用的第三方库有没有 synchronized?大概率有。JDBC 驱动、连接池、日志框架、序列化库,很多底层库在历史代码里用了 synchronized 而不是 ReentrantLock。这些库不改,你用虚拟线程就可能踩坑。

虽然这两年主流框架和库在逐步适配——HikariCP、Netty、Log4j2 都在做改造——但整个生态的适配是一个长期过程。你不可能把项目依赖的几十个库挨个审查一遍看有没有 synchronized 的问题。在适配完成之前,用虚拟线程就是在赌你的调用链上没有 pin 的点。


Spring 的态度也是"支持但不推"

Spring Framework 6 和 Spring Boot 3 已经支持虚拟线程了,配置一行 spring.threads.virtual.enabled=true 就能把 Tomcat 的线程改成虚拟线程。

但你注意 Spring 的做法——它没有把虚拟线程设成默认选项,也没有大力推广"赶紧用虚拟线程"。官方文档里的态度更接近"如果你需要,可以用"。

这不是 Spring 反应慢,而是他们清楚虚拟线程在当前生态下还有坑。Spring 生态里大量的用户在用各种 JDBC 驱动、Redis 客户端、HTTP 客户端,这些库的虚拟线程适配程度参差不齐。贸然把默认值改成虚拟线程,可能会让一部分用户的系统出问题。

Spring 的选择是稳妥的:先支持,让想尝鲜的人可以用,等生态成熟了再逐步推广。而大部分 Spring 用户的习惯是跟着 Spring 的默认配置走,Spring 不默认开,他们就不会主动去开。


对于大部分开发者来说,线程池够用且熟悉

这一点很现实。ThreadPoolExecutor 那一套,Java 开发者从入行就在学——核心线程数、最大线程数、队列策略、拒绝策略。面试要考,工作中要用,出了问题排查思路也清晰。

虚拟线程是一个新的编程模型。虽然 API 上看起来很简单(Thread.ofVirtual().start(task)),但要用好它需要理解 carrier 线程、pin 问题、structured concurrency(结构化并发)、scoped values 这些新概念。学习成本不高但也不低,而且学了之后在当前的项目里可能也用不上。

人的本性就是这样:现有方案能解决问题的时候,没有动力去学新东西。只有当现有方案真的撑不住了——比如你的服务确实需要处理几万个并发连接,线程池开不了那么多线程——你才会认真考虑虚拟线程。


虚拟线程的热度会来,但需要时间

回看 Java 的历史,每个重大特性的普及都有一个过程。NIO 从 JDK 1.4 引入,到 Netty 真正让它流行起来用了很多年。Stream API 从 JDK 8 引入,到大家自然地在项目里使用又花了三四年。

虚拟线程也会经历类似的过程。随着 JDK 21+ 的普及率提高、第三方库的适配完成、Spring 等框架开始默认启用、以及越来越多的实际案例证明它的价值,热度自然会上来。

现在没热度不代表这个特性不好,只是时间还没到。Java 生态的特点就是慢热但长久,一旦铺开了就会变成标准做法。急不来的。

上次编辑于: