一句话总结
虚拟线程(JDK21 正式)是 JVM 调度的轻量线程:阻塞时把栈从载体线程(OS 线程)上卸下(unmount),载体线程立刻去跑别的虚拟线程——M:N 调度让"百万级线程"成为可能,线程数不再受 OS 限制。定位:IO 密集任务的吞吐利器,代码保持同步直白风格(不用响应式那一套);CPU 密集没收益。两个注意点:synchronized 长代码块会 pin 载体线程(JDK24 已修复),以及不要池化虚拟线程。
初级理解
基本用法(三行上手):
| 对比项 | 平台线程 | 虚拟线程 |
|---|---|---|
| 载体 | 1:1 映射 OS 线程 | M:1N 挂在少量载体线程上 |
| 内存占用 | 约 1MB 栈(预分配) | 几百字节起,按需增长(堆上) |
| 创建成本 | 高(系统调用),千级 | 极低,百万级可行 |
| 阻塞代价 | 挂起 OS 线程,触发上下文切换 | 卸载栈,载体线程继续跑别的 |
| 适用场景 | CPU 密集、池化复用 | IO 密集、每请求/每任务一线程 |
中级深入
为什么阻塞变"免费"了:平台线程阻塞 = OS 上下文切换(微秒级 + 内核参与);虚拟线程在阻塞点(网络 IO、sleep、锁等待)由 JVM 把它的栈帧拷回堆内存,释放载体线程,就绪后再挂回某个载体线程继续跑。只有载体线程(数量 = CPU 核数)参与真正的 OS 调度。
pin(钉住)问题——高频追问:虚拟线程在两种情况下无法卸载:执行 synchronized 修饰的代码块/方法期间(JDK21 的实现限制;JDK24 JEP 491 已让 synchronized 可卸载)、调用 native 方法(JNI)期间。此时虚拟线程"钉"在载体上,载体线程被占死,等于退化成平台线程——用 synchronized 包住远程调用是典型的性能事故。替代:ReentrantLock(可卸载),或把 synchronized 块缩小到不含阻塞 IO。
ThreadLocal 的态度变化:百万虚拟线程各自带 ThreadLocal 副本 = 内存放大器,官方建议虚拟线程场景少用/换 ScopedValue(JEP 446 预览,作用域绑定、不可变、随作用域自动失效)。MDC/日志上下文要确认链路库的虚拟线程兼容性。
高级拓展
与响应式编程(WebFlux/Reactor)对比——核心论点:响应式靠"回调+事件循环"榨干少量线程,吞吐好但代码被 Completable/Flux 链统治,调试栈断片、心智成本高;虚拟线程用同步写法达到近似吞吐,try-catch、调试、_profile 都回归常态。选型:新 IO 密集服务优先虚拟线程;已有响应式体系且收益稳定则不必推翻。
结构化并发(Structured Concurrency,JEP 453/480 预览):把"一组相关的并发子任务"作为一个整体管理——一个失败全部取消、父任务等所有子任务结束,解决"线程泄漏与孤儿任务"。
工程接入与观测:Spring Boot 3.2+ 一行开关 spring.threads.virtual.enabled=true(Tomcat/Jetty 请求处理线程虚拟化);Tomcat 线程池配 max 后,瓶颈会转移到下游——数据库连接池要先扩(连接是真实的 OS 资源,虚拟线程把"线程不够"换成"连接不够")。观测用 JFR 的 jdk.VirtualThreadPinned 事件监控 pin 时长。
实战场景
场景一:聚合接口从 200 线程池切虚拟线程
场景二:并发限制从"线程池大小"换成 Semaphore
场景三:批量任务 pin 事故排查
面试模拟
Q:虚拟线程为什么不能替代线程池?为什么建议不池化?
A:线程池的存在意义有两个——复用昂贵的 OS 线程 + 用池大小限流。虚拟线程创建成本趋近于零,复用没意义(反而因 ThreadLocal 残留带来脏状态),所以官方模型是"每任务一个新虚拟线程";但限流职责还在——用 Semaphore、信号量化的连接池、或框架层的并发控制来替代池大小限流。所以准确说法是:它替代的是"线程池的复用职能",不替代"限流职能"。
Q:synchronized 的 pin 问题在 JDK24 之后还存在吗?
A:JEP 491(JDK24)之后 synchronized 不再 pin——虚拟线程阻塞在 synchronized 上也能正常卸载。但在 JDK21~23 上仍需规避:热点路径的 synchronized 包住阻塞 IO 会钉死载体线程。JNI/native 调用期间的 pin 依然存在(这是平台机制决定的)。生产排查靠 JFR 的 pinned 事件,不要靠猜。
Q:有了虚拟线程,还需要 WebFlux 这类响应式框架吗?
A:大多数业务场景不需要了——虚拟线程用同步代码达到同量级吞吐,可维护性显著更好。响应式框架仍有价值的地方:背压(流控)、纯事件驱动的极低延迟场景、以及已是 Reactor 技术栈的存量系统。选型逻辑:优先"简单直白 + 虚拟线程",让复杂度(背压/事件驱动)成为迫不得已的选择而不是默认。