主题
界面
主题
并发的终极形态:海量阻塞 IO 不再需要池化。
★ 本节实测环境为 JDK 25(虚拟线程自 JDK 21 正式可用,JEP 444;基线差异说明见 《JDK 与 Java 版本》):
10 万个虚拟线程(各 sleep 200ms)→ 1166 ms 全部完成 ✓✓✓
平台线程:10 万个 × 约 1MB 栈 ≈ 100GB 内存 → 不可能 ✗✗★ 平台线程的天花板 ✗✗(项目 B 实测过 200 并发的上限):
每个平台线程占约 1MB 栈 + 内核资源 → 线程数 = 内存数 ✗
而 IO 密集服务里,线程 99% 时间在等 IO ✗✗
虚拟线程把「等待」变成「挂起」——等的时候不占载体线程 ✓✓✓
虚拟线程(海量,便宜)
└─ 挂在「载体线程」(少量平台线程,= CPU 核数)上运行
阻塞时:虚拟线程「卸载」,载体线程去跑别的虚拟线程 ✓✓✓★ 类比:平台线程是「厨师」,虚拟线程是「订单」—— 阻塞 IO 相当于「等外卖」:BIO 模型下一个厨师等一单到底 ✗✗; 虚拟线程让厨师在等待时去做别的单 ✓✓
| 平台线程 | 虚拟线程 | |
|---|---|---|
| 10 万个 | 内存 ≈ 100GB ✗✗ | 1166 ms 完成 ✓✓✓ |
| 创建成本 | 重(MB 级栈) | 轻(KB 级,堆上对象) ✓ |
| 阻塞代价 | 占一个线程 ✗ | 挂起,释放载体 ✓✓ |
| 需要池化 | 是(池化复用) | 不需要(用完即弃)✓✓✓ |
| 场景 | 结论 |
|---|---|
| HTTP 客户端调用、JDBC 查询、文件读写 ★★★ | ✓✓✓ 最佳(阻塞 IO) |
| CPU 密集计算 ✗✗ | 无收益(虚拟线程不增加 CPU)——用线程池 = 核数 ✓ |
| 秒杀扣库存(项目 B)✗ | 瓶颈是串行资源,虚拟线程反而放大并发打向数据库 ✗✗ |
★★★ 一句话:虚拟线程解决「线程不够用」,不解决「资源不够用」 ✓✓✓
虚拟线程在 synchronized 块内阻塞时,会「钉住」载体线程不释放 ✗
→ 载体被占满,其他虚拟线程饿死 ✗✗JDK 24 已大幅缓解(synchronized 内阻塞不再永久钉住)✓; 但 JDBC 驱动等本机调用仍可能钉住 ⚠️ —— 监控:
-Djdk.tracePinnedThreads=full✓✓
★ 迁移注意 ✓:ThreadLocal 大量使用的服务迁移虚拟线程要小心(百万虚拟线程 × 大 ThreadLocal = 内存爆炸)✗✓
// ① 直接起
Thread.ofVirtual().start(() -> handle(request));
// ② 执行器(Spring Boot 3.2+ 一行配置)
spring.threads.virtual.enabled=true
// Tomcat 改为每请求一个虚拟线程 ✓✓✓★ 与项目 B 的关系 ✓✓✓:
项目 B 用 200 平台线程池 → 并发上限 200 ✗
虚拟线程 → 并发上限 ≈ 内存/文件句柄(几十万)✓✓✓
但数据库连接池不会变 ✗ ——瓶颈只是后移(先连接池后数据库)✓✓ (呼应 《项目 B:性能测试与故障分析》 的「先坏清单」✓)
Thread.ofVirtual().name("io-", 0).start(() -> callRemote());spring.threads.virtual.enabled=true
# 现有代码不变,Tomcat 每请求一个虚拟线程 ✓✓✓Thread.ofVirtual().start(() -> encryptBigFile()); // ✗ 无收益还有调度开销代码:labs/java-deep/src/main/java/cn/kb/learn/deep/concurrency/JucVirtualThreadLesson.java
实测输出:
== 3. 100k virtual threads, each sleeping 200ms ==
done in 1166 ms-Djdk.tracePinnedThreads=full 找钉住点 ✓。| # | 错误 | 说明 |
|---|---|---|
| 1 | CPU 密集任务用虚拟线程 ✗✗ | 无收益 ✓✓ |
| 2 | 以为虚拟线程能省数据库连接 ✗✗ | 连接池是另一个瓶颈 ✓✓ |
| 3 | 给虚拟线程配池 ✗✗ | 反模式(设计目标就是不用池)✓✓ |
| 4 | ThreadLocal 塞大对象 ✗✗ | 百万级放大 ✓ |
| 5 | synchronized 内长时间阻塞 ⚠️ | 可能钉住载体(旧 JDK)✓ |
virtual.enabled=true,压测对比 ✓✓。验收方式:能说出「你的服务哪些环节适合迁移虚拟线程、哪些不适合」。
扩展任务:把项目 A 的借书接口改成虚拟线程版并压测对比 ✓✓
-Djdk.tracePinnedThreads 定位 ✓★暂无