一、线程的困境
传统 Java 应用中,每个请求往往对应一个操作系统线程(Thread-per-Request 模型)。当并发量上升到数千甚至数万时,问题就出现了:
- 内存开销大:每个 OS 线程默认栈大小约 1MB,10000 个线程就是 10GB 内存
- 上下文切换昂贵:CPU 在线程间频繁切换,有效工作时间被稀释
- 创建销毁慢:OS 线程的创建和销毁是系统调用,耗时较长
``java
// 传统写法:线程池 + CompletableFuture
ExecutorService executor = Executors.newFixedThreadPool(200);
CompletableFuture.supplyAsync(() -> callRemoteService(), executor)
.thenAccept(result -> processResult(result));
`
线程池虽然缓解了创建销毁的开销,但请求量大时,排队等待仍然不可避免。
二、虚拟线程是什么
虚拟线程(Virtual Thread)是 JDK 19 预览、JDK 21 正式发布的特性,由 Project Loom 落地。核心思想:将 Java 线程从 OS 线程中解耦。
关键特性
内部原理
虚拟线程的底层由一个 ForkJoinPool 承载,每个虚拟线程本质上是一个 continuation(续延)。当虚拟线程执行阻塞 I/O 时:
- JVM 检测到阻塞调用
- 卸载(unmount)该虚拟线程,释放载体线程
- 载体线程立即去执行其他虚拟线程
- I/O 就绪后,虚拟线程重新挂载(mount)继续执行
`java
// 虚拟线程写法 —— 近乎同步代码的简洁,异步执行的性能
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var futures = IntStream.range(0, 10000).mapToObj(i ->
executor.submit(() -> {
var response = callRemoteService(i); // 阻塞调用
return processResponse(response);
})
).toList();
futures.forEach(f -> {
try {
f.get();
} catch (Exception e) {
// handle
}
});
}
`
三、实战压测
我们用 Spring Boot 3.2 + JDK 21 搭建一个简单的 I/O 密集型服务,对比平台线程与虚拟线程的表现。
测试场景
- 模拟 100ms 的 I/O 延迟(数据库查询、RPC 调用)
- 1000 并发请求
- 对比指标:吞吐量(RPS)、P99 延迟、内存使用
配置
`yaml
`application.yml —— 启用虚拟线程
spring:
threads:
virtual:
enabled: true
结果
虚拟线程在高 I/O 并发场景下,吞吐量提升 2.6 倍,内存占用降低 60%。
四、结构化并发
随虚拟线程一起到来的还有 结构化并发(Structured Concurrency, JEP 453)。
传统 CompletableFuture 的问题是:当父任务取消时,子任务可能继续运行,造成资源泄漏。
`java
// 结构化并发 —— 子任务的生命周期被严格限定在 try-with-resources 块内
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier
scope.join(); // 等待所有子任务完成 scope.throwIfFailed(); // 任一失败则传播异常
return new Response(user.get(), order.get());
}
// 离开 try 块后,所有子任务保证终止
`
五、适用场景与注意事项
✅ 适合虚拟线程的场景
- I/O 密集型服务:Web 服务、API 网关、数据管道
- 高并发轻任务:简单查询、短连接处理
- 同步代码重构:无需改造成异步即可获得高吞吐
❌ 不适合的场景
- CPU 密集型:计算、加密、压缩 → 虚拟线程不会提升性能
- pin 操作:synchronized 块内长时间执行会"钉住"载体线程
- 已经用 Netty/Vert.x 重构好的项目:异步模型本身已很高效,不必强制迁移
避坑指南
`java
// ❌ 不要在虚拟线程中使用线程局部变量堆大对象
ThreadLocal
// ✅ 使用对象池或小对象
ObjectPool
六、总结
虚拟线程不是银弹,但它极大降低了 Java 高并发编程的门槛。过去必须用 Reactive 编程(WebFlux)才能支撑的并发量,现在用同步代码就能做到。
"Write simple, run fast" — 虚拟线程让这句话在 Java 世界成为现实。
下一步:检查你的项目中哪些服务是 I/O 密集型的,优先尝试切换到虚拟线程。只需升级到 JDK 21 + Spring Boot 3.2,改一行配置即可开始。