Java 虚拟线程是什么?能代替线程池、让 CPU 密集任务更快吗?
下面是一段教学用的模拟面试。
🧑💻 面试官: Java 虚拟线程能解决什么问题?
🙋♂️ 我: 线程创建便宜,可以开很多个,所以程序会更快。
🧑💻 面试官: 一万个线程一起计算大数,CPU 只有八个核,会发生什么?
🙋♂️ 我: 还是要争抢有限的 CPU。
🧑💻 面试官: 如果都去查询数据库,连接池只有五十个呢?线程便宜,是否等于下游也变大了?
虚拟线程让“等待”更便宜,不会把 CPU、数据库和远端服务一起扩容。
面试速答(60 秒版)
Java 虚拟线程是由 Java 运行时调度的轻量线程,JDK 21 正式提供。它运行时仍要使用平台线程,只是不必在整个生命周期里一直占住同一个操作系统线程。
对于运行时能够挂起的阻塞 I/O,虚拟线程等待时可以卸载,把承载它的平台线程让给其他任务。它适合大量并发、等待占比高的请求,不会自动加快一段 CPU 密集计算。
使用时通常是一任务一虚拟线程,而不是把虚拟线程当成稀缺资源再池化。但数据库连接、远端调用等真正稀缺的资源,仍需要限制并发、设置超时。
版本也要说清楚:JDK 24 的 JEP 491 改进了 synchronized 相关的 pinning 问题。不能拿 JDK 21 的全部限制描述后面的版本。

图:虚拟线程让等待更便宜。
知识点详解:线程等待的时候,谁还被占着?
先看一个等待很多的场景
假设接口需要调用远端服务,等待返回的时间远大于本地处理时间。并发请求增加后,如果每个请求都用一个平台线程等待,就会占用很多线程及其资源。
虚拟线程希望保留顺序书写的阻塞式代码,同时让大量等待任务不必分别长期占住一个平台线程。这个收益主要来自等待过程,不是让远端服务更快。
因此,它提高的是合适负载下的并发承载能力。单次网络请求的等待时间,并不会因为换了线程名字就凭空缩短。机制与适用范围见 Oracle 虚拟线程指南。
虚拟线程和 carrier 怎么配合?
虚拟线程真正执行 Java 代码时,会挂载到一个承载它的平台线程,也就是 carrier 上。
遇到能够卸载的等待,运行时保存虚拟线程的执行状态,释放 carrier。另一个就绪的虚拟线程随后可以使用这个 carrier。等原任务可以继续了,再重新调度。
这里不是两个虚拟线程同时在一个 carrier 上执行,也不是所有阻塞情况都无条件卸载。实际库和调用路径仍然要检查。
为什么 CPU 密集任务不会因此变快?
假设计算总共需要很多 CPU 指令。把任务拆成更多虚拟线程,不会增加物理核,也不会让每条指令执行得更快。
如果线程不断争抢 CPU,调度和其他开销还可能增加。CPU 密集任务需要根据计算量和机器资源设计并行度,而不是用“能创建很多线程”作为加速依据。
不池化线程,为什么还要限流?
线程数量和资源容量是两回事。数据库只有五十个连接,一万个虚拟线程同时等待连接,并不会得到一万个可用连接。
需要限制的是数据库、远端接口等真正稀缺资源。超时、取消和等待队列也要设计,否则便宜的等待会变成堆积大量任务、内存和请求上下文。
ThreadLocal 也不是禁止使用,但每个任务保留大量数据,再乘上很高的并发,就可能变成显著内存开销。

图:一万个任务,不等于一万个连接。
pinning 为什么必须带版本回答?
较早版本里,某些 synchronized 内的阻塞会把虚拟线程固定在 carrier 上,影响扩展能力。JDK 24 通过 JEP 491 改进了这一类问题,见 JDK 24 重要变化。
但这不等于后续版本完全不存在 pinning。原生调用等情况仍有边界。先确认 JDK、库版本与具体调用路径,再通过 JFR 等工具观察,不能拿旧口诀直接重构全部锁。
本题解释 Java 专属调度,不把 TS Promise 或 Python asyncio 当成同一种实现。
面试官继续追问
怎么判断迁移有没有价值?
在同样的负载、机器和下游限制下,分别测吞吐、尾延迟、内存和等待数量。本文没有用假设场景冒充实际压测结果。
原来的线程池能直接全删吗?
不能。先区分池子是在控制线程成本,还是同时承担任务排队、拒绝和资源限制。后者即使换成虚拟线程,也需要找到替代设计。
面试速记卡
- 目标:降低大量等待任务占用平台线程的成本。
- 执行:仍依赖 carrier 和实际 CPU。
- 适用:大量并发、I/O 等待占比高。
- 限制:线程便宜,下游容量、内存和超时仍有限。
- 版本:JDK 21 正式提供,JDK 24 改进 synchronized pinning。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →