虚拟线程(Virtual Thread)原理
Java 21 引入了虚拟线程(Virtual Thread),这是 Java 并发模型的重大变革。我第一次看到这个特性的时候,感觉像是打开了新世界的大门。
以前我们说 Java 能轻松创建线程,但实际上每个线程都要占用约 1MB 的栈内存,10000 个线程就要 10GB 内存。虚拟线程的出现,让"百万并发"变成了可能。
今天我们就来把虚拟线程彻底讲透。
一、为什么需要虚拟线程
1.1 传统线程的问题
问题:
- 每个线程约 1MB 栈内存
- 10000 个线程 = 10GB 内存
- 大部分时间在线程等待,资源浪费
1.2 传统并发模型的困境
1.3 虚拟线程的解决方案
二、虚拟线程 vs 平台线程
2.1 概念对比
2.2 【直观类比】
【直观类比】
把线程比作"人":
2.3 内部实现
三、Continuation:挂起的核心
3.1 Continuation 是什么
虚拟线程的挂起机制基于 Continuation(续体)。当虚拟线程阻塞时,它会"挂起",把栈帧保存到堆中,而不是阻塞 OS 线程。
3.2 挂起过程
3.3 挂起 vs 阻塞
四、创建和使用虚拟线程
4.1 创建虚拟线程
4.2 线程池对比
4.3 虚拟线程适合的场景
五、Pinning 问题
5.1 什么是 Pinning
虚拟线程挂起依赖于 synchronized 块的释放。但如果 synchronized 块持有内部锁,JVM 无法挂起虚拟线程,导致 Carrier 线程被阻塞。
5.2 Pinning 的影响
5.3 解决方案
六、生产最佳实践
6.1 ✅ 推荐:使用虚拟线程的场景
6.2 ✅ 推荐:重构现有代码
6.3 ❌ 避免:在线程池中使用 ThreadLocal
七、性能对比
7.1 内存占用对比
7.2 吞吐量对比
八、面试追问链
第一层:基础概念
面试官问:"虚拟线程是什么?"
Java 21 引入的轻量级线程,由 JVM 管理而不是 OS 线程。虚拟线程的栈在堆中按需增长,阻塞时挂起到堆中,让 Carrier 线程去执行其他虚拟线程。
第二层:原理
面试官追问:"虚拟线程是怎么实现挂起的?"
基于 Continuation 机制。当虚拟线程阻塞时,JVM 把它的栈帧保存到堆中,切换到 Carrier 线程执行其他虚拟线程。阻塞结束后,虚拟线程从堆中恢复栈帧继续执行。
第三层:与 OS 线程的区别
面试官追问:"虚拟线程和平台线程有什么区别?"
平台线程是 OS 线程的 1:1 映射,每个平台线程占用约 1MB 内存。虚拟线程是 JVM 管理的,多个虚拟线程可以复用一个 Carrier 线程(OS 线程)。虚拟线程创建成本极低,适合大量并发。
第四层:Pinning 问题
面试官追问:"什么是 Pinning?怎么解决?"
synchronized 块持有内部锁时,JVM 无法挂起虚拟线程,导致 Carrier 线程被阻塞。解决方案是改用 ReentrantLock,或者升级到 Java 21+。
【学习小结】
- 虚拟线程是 JVM 管理的轻量级线程
- 栈在堆中按需增长,占用内存少
- 阻塞时挂起,Carrier 线程执行其他虚拟线程
- 适合 IO 密集型、大量并发场景
- 不适合 CPU 密集型
- synchronized 会导致 Pinning,Java 21+ 可用 ReentrantLock