Java 中的虚拟线程:可扩展性与复杂性平衡
传统并发的问题
Java 应用程序传统上依赖 平台线程(Platform Threads 或传统线程)进行并发。
平台线程(PT):
- 作为操作系统管理线程的包装器,具有 1 映射。
- 昂贵且重量级,每个线程消耗约 1-2 MB 内存和操作系统资源。
- 受系统资源限制。创建过多会导致
OutOfMemoryError或使上下文切换饱和。 - 在 Web 应用中,"每个请求一个线程"(Thread-per-Request)模型直观但无法很好地扩展高并发(例如 >10k 并发请求)。
需求: 在微服务、流处理或交易 API 等高并发场景中,需要低延迟和高可扩展性。响应式编程(Project Reactor、RxJava)等解决方案提高了吞吐量,但使代码和调试复杂化。
什么是虚拟线程(VT)?
由 Project Loom 引入并在 JDK 21(JEP 444)中稳定化的虚拟线程是 Java 解决大规模并发而不使用异步编程的方案。
主要特性:
- 轻量级线程: 由 JVM 管理的
java.lang.Thread实例(约 1 KB 每线程)。 - 数百万线程: 允许创建 数百万 线程而不耗尽内存。
- I/O 绑定可扩展性: 针对阻塞任务(非 CPU 绑定)的吞吐量优化。
- 完全兼容: 现有 Thread 代码的即插即用替代品。类似于 Go 的 goroutines。
- JDK 21+ 可用: 自 JDK 21 稳定;JDK 22-25 持续改进。
内部工作原理:M 调度
M
映射: M 个虚拟线程(数百万)映射到 N 个平台线程(CPU 核心)。- 载体: 承载虚拟线程的平台线程。
- 挂载/卸载: 虚拟线程挂载到载体上执行;在 I/O 阻塞时(
sleep()、套接字、数据库查询)卸载。 - JVM 调度器: 动态重新分配载体。载体在 I/O 等待期间保持空闲。
- 延续: I/O 完成后,虚拟线程返回调度器并在任何可用载体上重新挂载。
这最大化了 CPU 利用率:载体始终忙碌。
最新更新:
- JDK 24(JEP 491): 消除了
synchronized块中的 固定(虚拟线程正确卸载)。 - JDK 25(JEP 506): 作用域值作为
ThreadLocal的不可变替代品,防止虚拟线程中的内存泄漏。
实际示例
针对 I/O 绑定(HTTP、数据库、文件)优化。
示例 1:简单虚拟线程
Thread.startVirtualThread(() -> {
System.out.println("来自 VT 的问候!" + Thread.currentThread());
});示例 2:ExecutorService(高并发)
import java.util.concurrent.Executors;
import java.time.Duration;
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 10k 并发任务
IntStream.range(0, 10_000).forEach(i ->
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1)); // 模拟 I/O
// 业务逻辑
})
);
} // 自动等待完成示例 3:Spring Boot + VT(真实基准测试)
在最近的基准测试中(JDK 24 + Spring Boot + Postgres),虚拟线程在 10k req/s 时将延迟降低约 50%,相比平台线程。
@RestController
public class ApiController {
@GetMapping("/data")
String getData() {
// VT 自动处理请求(Tomcat/Undertow 配置)
return fetchFromDb(); // 阻塞但可扩展
}
}高级考虑和最佳实践
CPU 绑定 vs I/O 绑定
| 类型 | 推荐 | 原因 |
|---|---|---|
| I/O 绑定(数据库、HTTP) | VT + newVirtualThreadPerTaskExecutor() | 大规模可扩展性 |
| CPU 绑定(加密、机器学习) | 平台线程池 = CPU 核心数 | 避免 载体饥饿 |
问题和解决方案
- 固定: JDK 24 中
synchronized已解决。避免 JNI/原生代码。 - ThreadLocal: ❌ 与 VT(内存爆炸)。使用 作用域值(JDK 25 预览版)。
- 指标/监控:
Thread.currentThread().isVirtual();JFR 自 JDK 21 支持 VT。 - 框架: Spring Boot 3.2+、Quarkus、Helidon 原生。
最佳实践(2025):
- 在 Web 服务器中默认使用 VT。
- 使用 JDK Mission Control/JFR 分析以检测固定。
- 将
ThreadLocal迁移到作用域值。 - CPU 密集型使用固定池。
- 使用 wrk/ab 测试 >100k 并发。
最终总结
| 档次 | 使用 VT? |
|---|---|
| 初学者 | 是,Thread.startVirtualThread() 用于所有并发。 |
| 专家 | I/O 使用是;纯 CPU 使用否。监控固定。 |
比喻: PT = 每人专用电梯(昂贵)。VT = 气动管道系统:数千个轻量胶囊在少数物理管道中。
来源:OpenJDK JEPs、Spring Boot 基准测试(Reddit/Java 24)、RockTheJVM/RabinaNPatra 指南(2025)。