虚拟线程在 Java / Spring Boot 项目里到底怎么写、怎么用、怎么替代传统线程、解决了哪些老问题。
环境要求:
虚拟线程就是为解决上面所有问题而生的。
虚拟线程不需要线程池,来一个任务开一个虚拟线程,百万线程也不会 OOM。
// 传统写法:启动平台线程
new Thread(() -> {
// 业务逻辑
}).start();
// 虚拟线程写法(JDK21)
Thread.startVirtualThread(() -> {
// 你的业务:DB查询、HTTP调用、文件读写
System.out.println("虚拟线程运行:" + Thread.currentThread().getName());
});
// 传统线程池(有上限、会满、会阻塞)
ExecutorService traditionalPool = Executors.newFixedThreadPool(20);
// 虚拟线程执行器(无上限、轻量、自动复用平台线程)
ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
使用方式和原来完全一样,无痛替换:
// 提交任务
virtualExecutor.submit(() -> {
// IO 密集型业务:查询数据库、调用第三方接口、导出文件
userMapper.selectList(null);
});
ThreadFactory factory = Thread.ofVirtual()
.name("my-virtual-thread-", 0)
.factory();
// 使用工厂创建
Thread t = factory.newThread(() -> {
System.out.println("自定义名称虚拟线程");
});
t.start();
不需要改任何 Controller、Service 业务代码,只加一行配置。
spring:
threads:
virtual:
enabled: true # 开启虚拟线程
效果:
@Async 异步方法使用虚拟线程如果你用 @Async,默认还是平台线程池,手动替换成虚拟线程:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
// 直接返回虚拟线程执行器,替换原有 ThreadPoolTaskExecutor
return Executors.newVirtualThreadPerTaskExecutor();
}
}
业务代码完全不变:
@Service
public class ExportService {
// 异步导出订单(虚拟线程执行)
@Async
public void asyncExportOrder() {
// 查询DB + 生成文件 + 上传MinIO
List<Order> list = orderMapper.selectList(null);
writeToCsv(list);
}
}
传统问题:
虚拟线程:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/get")
public User getUser(Long id) {
// 虚拟线程内执行
// DB IO 阻塞 → 自动释放平台线程
return userMapper.selectById(id);
}
}
解决的问题:
传统问题:
虚拟线程写法:
@Autowired
private Executor virtualExecutor;
public void batchExport(List<Long> userIds) {
for (Long userId : userIds) {
// 来一个任务开一个虚拟线程,无压力
virtualExecutor.submit(() -> {
// 查询 + 生成文件 + 上传MinIO
exportUserFile(userId);
});
}
}
解决的问题:
List<String> urlList = ...;
for (String url : urlList) {
virtualExecutor.submit(() -> {
// 阻塞等待第三方返回
String result = restTemplate.getForObject(url, String.class);
saveResult(result);
});
}
解决的问题:
| 问题 | 传统平台线程 / 线程池 | 虚拟线程 |
|---|---|---|
| 线程数量上限 | 几百~几千,多了 OOM | 百万级无压力 |
| IO 阻塞(DB/HTTP/ 文件) | 线程挂起,空占资源 | 自动挂载,释放平台线程 |
| 线程池参数调优 | 极难,经常满 / 拒绝 | 无需线程池,不用调参 |
| 内存占用 | 高(每个线程栈 1M) | 极低(几百字节) |
| 高并发下吞吐量 | 低,容易被阻塞拖死 | 极高,IO 越多优势越大 |
synchronized 不会释放平台线程(大坑)// 虚拟线程阻塞在这里 → 平台线程被占住,不释放
synchronized (lock) {
// ...
}
替换方案:高并发路径用 ReentrantLock,虚拟线程阻塞时会让出平台线程。
ThreadLocal 要慎用虚拟线程数量巨大,ThreadLocal 会导致内存占用飙升。Spring 内部已经帮你处理了 RequestContext 等,但自己写的要小心。
虚拟线程本来就是一次性的,再放进线程池完全违背设计思想。直接用:
Executors.newVirtualThreadPerTaskExecutor()
spring.threads.virtual.enabled=trueExecutors.newVirtualThreadPerTaskExecutor()ThreadPoolExecutor