一、 核心概念与底层原理
1. 什么是虚拟线程?
虚拟线程是 JDK 21 正式引入的轻量级并发原语。它不是操作系统级别的线程,而是由 JVM 在用户态管理的极轻量级对象。其核心目标是以极低的成本支持超高并发,彻底解决传统线程模型在 I/O 密集型场景下的瓶颈。
2. M:N 调度模型
- 传统模型 (1:1):一个 Java 线程(Platform Thread)对应一个操作系统线程。OS 线程的创建、销毁和上下文切换成本极高(通常占用 1MB 内存),导致并发上限通常在几千左右。
- 虚拟线程 (M:N):M 个虚拟线程复用 N 个载体线程(Carrier Thread)。载体线程的数量通常等于 CPU 核心数。虚拟线程极其轻量,创建百万级虚拟线程毫无压力。
3. 核心运行机制:挂载与卸载 (Mount/Unmount)
虚拟线程的调度不依赖操作系统的抢占式调度,而是基于协作式调度:
- 挂载 (Mount):虚拟线程被绑定到一个载体线程上,此时它真正占用了 CPU 核心执行代码。
- 卸载 (Unmount) :当虚拟线程执行到阻塞操作(如 I/O 读写、
LockSupport.park()、Thread.sleep())时,JVM 会自动将其从载体线程上"摘除"。 - 恢复 (Remount):阻塞操作完成后,JVM 会自动将其重新挂载到某个空闲的载体线程上继续执行。
4. 调度器:ForkJoinPool 与 Work-Stealing
- 虚拟线程默认运行在一个全局的
ForkJoinPool中。 - 工作窃取 (Work-Stealing):当某个载体线程的本地队列为空时,它会主动从其他繁忙的载体线程队列中"偷"任务来执行,从而最大化 CPU 利用率,避免线程饥饿。
二、 为什么需要虚拟线程?(对比分析)
表格
| 维度 | 传统平台线程 (Platform Thread) | 协程/手动让出 (如 Kotlin Coroutines) | 虚拟线程 (Virtual Threads) |
|---|---|---|---|
| 内存开销 | 极高(~1MB/线程) | 极低(几KB) | 极低(初始仅几百字节) |
| 并发上限 | 数千 | 数十万 | 数百万 |
| 编程范式 | 同步阻塞代码 | 异步/挂起函数(需学习新语法) | 纯同步阻塞代码 |
| 阻塞处理 | 阻塞 OS 线程,浪费 CPU | 需手动标记 suspend 关键字 |
自动检测并让出载体 |
| 生态兼容 | 完美 | 需特定框架支持 | 完美兼容现有 JDK API |
核心优势总结 :虚拟线程让开发者用写传统同步阻塞代码的方式,获得媲美异步非阻塞(Reactive)的高并发性能。
三、 最佳实践与使用方式
1. 创建与执行
// 方式 1:直接启动(适用于少量临时任务)
Thread.startVirtualThread(() -> {
System.out.println("Hello Virtual Thread!");
});
// 方式 2:使用 ExecutorService(强烈推荐,便于资源管理和优雅关闭)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 100_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1)); // 阻塞时自动让出载体
return i;
});
});
} // try-with-resources 会自动等待所有任务完成并关闭
2. 核心使用原则(⚠️ 避坑指南)
- 不要池化虚拟线程 :虚拟线程是"用完即弃"的消耗品,创建和销毁成本极低。永远不要使用固定大小的线程池来运行虚拟线程,应使用
newVirtualThreadPerTaskExecutor(),让每个任务独占一个虚拟线程。 - 仅适用于 I/O 密集型任务:虚拟线程的优势在于处理高并发 I/O(网络请求、数据库查询、文件读写)。对于 CPU 密集型任务,虚拟线程没有性能优势,甚至可能因为上下文切换带来额外开销。
- 避免长时间 Pinning(固定) :如果在
synchronized块或native方法中发生阻塞,虚拟线程无法卸载,会连带把载体线程一起阻塞。应尽量使用ReentrantLock替代synchronized。
四、 适用场景与局限性
绝佳适用场景
- 高并发微服务网关:处理海量 HTTP 请求。
- 数据聚合服务:同时调用数十个下游微服务并等待结果。
- 爬虫与批量数据处理:高并发的网络 I/O 操作。
- 传统遗留系统改造:无需重构为 WebFlux/RxJava,只需升级 JDK 并替换线程池,即可获得数倍的吞吐量提升。
不适用场景
- 纯 CPU 密集型计算:如视频编解码、复杂数学运算(应使用传统线程池或并行流)。
- 强依赖底层 OS 线程绑定的场景:如某些需要绑定特定 CPU 核心的底层系统编程。