上个月公司有个内部服务要做性能基线评估技术栈刚好迁移到了 Spring Boot 3.2JDK 版本也升到了 21。既然虚拟线程Virtual ThreadsProject Loom已经正式 GA就顺手做了一次对比压测看看在真实的 I/O 密集型场景下虚拟线程相比传统平台线程到底能榨出多少性能余量。背景与动机我们有一个典型的网关聚合服务上游接收 App 请求下游并行调用 4-5 个内部微服务最后合并结果返回。逻辑不复杂但 I/O 等待时间占整个请求生命周期的 80% 以上是非常标准的 I/O 密集型场景。之前的配置是传统的 Tomcat 平台线程池线程数配得保守最大 200原因是线程栈内存占用高开多了容易把容器内存打满。但线程数保守的代价也很明显高并发下请求排队P99 延迟飙升。JDK 21 的虚拟线程理论上可以解决这个矛盾轻量级、由 JVM 调度、阻塞时自动让出载体线程看起来非常适合我们的场景。但官方文档也提醒过虚拟线程不是银弹某些同步原语比如synchronized块或ReentrantLock可能会钉住pin载体线程导致调度优势打折扣。所以决定不只看文档直接上压测。测试环境应用框架Spring Boot 3.2.0JDKEclipse Temurin 21.0.1容器Docker限制 4C 8G压测工具JMeter500 并发线程持续 5 分钟下游模拟一个独立服务固定延迟 100ms模拟内部调用Spring Boot 3.2 开启虚拟线程非常简单application.properties里加一行properties复制spring.threads.virtual.enabledtrue不需要改任何业务代码Tomcat 会自动用虚拟线程处理请求。测试方案做了三组对照平台线程 同步阻塞Tomcat 传统线程池下游用RestTemplate同步调用平台线程 异步非阻塞Tomcat 传统线程池下游用WebClient异步调用虚拟线程 同步阻塞Tomcat 虚拟线程下游用RestTemplate同步调用第三组是重点观察对象——虚拟线程最大的优势就是可以让同步代码写出异步的性能如果业务代码完全不用重构就能拿到收益那迁移成本最低。关键指标对比压测过程中用 Prometheus Grafana 抓了以下几组数据吞吐量Throughput平台线程 同步约 1,200 QPS达到线程池上限后不再增长平台线程 异步约 3,800 QPS事件循环发挥了作用虚拟线程 同步约 3,600 QPS虚拟线程组的吞吐量非常接近异步非阻塞方案但业务代码完全保持同步风格可读性和调试友好度明显更好。延迟分布Latency重点关注 P99平台线程 同步P99 达到 2.3s大量请求在 Tomcat 队列里排队平台线程 异步P99 约 280ms虚拟线程 同步P99 约 310ms虚拟线程的 P99 略逊于异步方案但差距不大。相比传统同步方案延迟表现提升了将近一个数量级。内存占用Memory平台线程 同步容器内存稳定在 3.2G 左右200 个平台线程的栈内存开销固定平台线程 异步约 2.8G异步上下文对象比线程栈轻一些虚拟线程 同步约 2.1G且压测过程中内存曲线非常平稳这是虚拟线程最超预期的地方。平台线程默认栈大小 1MB200 个线程就是 200MB 起步虚拟线程栈是动态分配的初始只有几百字节500 并发下内存压力反而更小。踩到的坑Carrier Thread Pinning压测过程中注意到一个细节虚拟线程组的吞吐量在并发打到 400 以上时出现了短暂的毛刺不是线性增长。用 JDK 21 新增的 JVM 参数打印钉住事件bash复制-Djdk.tracePinnedThreadsfull日志里发现业务代码里有一段历史债务——下游调用前加了synchronized块做本地缓存的互斥更新。虚拟线程执行到这段代码时会钉住底层的载体线程ForkJoinPool 里的平台线程导致该载体线程无法调度其他虚拟线程相当于退回了平台线程的调度模式。定位后把那段synchronized改成了ReentrantLock但ReentrantLock在 JDK 21 里同样会钉住载体线程。最后改成了无锁的ConcurrentHashMap::computeIfAbsent毛刺消失吞吐量曲线恢复平滑。这个坑说明虚拟线程虽然兼容旧代码但如果代码里有大量同步块或锁竞争性能优势会被稀释。迁移前最好全局扫一遍synchronized和Lock的使用。另一个观察ThreadLocal 的代价虚拟线程是ThreadLocal的天敌。压测时发现一个请求链路里某个库用ThreadLocal存了上下文对象虚拟线程频繁创建销毁时ThreadLocal的清理开销被放大GC 频率明显变高。JDK 21 提供了ScopedValue作为替代方案但涉及到底层库的改造短期没法落地。目前的权宜之计是减少虚拟线程的创建频率比如配合连接池复用而不是每个请求都新建。结论与落地建议基于这次压测我们对虚拟线程的落地策略做了调整适用场景I/O 密集型、阻塞操作多、业务代码以同步风格为主的服务虚拟线程收益最大。计算密集型或锁竞争激烈的场景优势不明显。迁移成本Spring Boot 3.2 的开关式启用确实做到了零代码改动但上线前必须做两件事全局扫描synchronized块以及评估ThreadLocal的使用密度。性能预期在理想的 I/O 密集型场景下虚拟线程可以接近异步非阻塞的吞吐量同时保持同步代码的可维护性。目前虚拟线程方案已经在测试环境跑了两个月计划下个季度切到生产。有在做类似评估的同学欢迎交流数据。一点补充这次压测让我意识到JDK 21 的虚拟线程不是一个开开关就能飞的魔法选项。它的价值在于降低了高并发 I/O 场景的编程模型复杂度让开发者可以用同步的思维方式写出接近异步的性能。但底层载体线程的调度、钉住事件、ThreadLocal 清理这些细节仍然需要开发者对运行时行为有清晰的理解。技术选型从来不是在新特性和稳定性之间二选一而是看新特性在特定场景下的边际收益是否大于迁移和学习的边际成本。就我们这次评估而言虚拟线程的收益是正的。