文档
Java 进阶篇:虚拟线程与并发编程
背景
JDK 21 引入了虚拟线程(Virtual Threads,Project Loom),彻底改变了 Java 的并发模型。与传统平台线程不同,虚拟线程由 JVM 管理,轻量到可以创建百万个。
核心概念
1. 虚拟线程 vs 平台线程
// 传统平台线程 — 重量级(约 1MB 栈内存/线程)
Thread.ofPlatform().start(() -> {
System.out.println("Platform thread: " + Thread.currentThread());
});
// 虚拟线程 — 轻量级(可创建百万个)
Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread: " + Thread.currentThread());
});
2. 使用 ExecutorService
// 为每个任务创建一个虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var futures = IntStream.range(0, 1000)
.mapToObj(i -> executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100)); // 虚拟线程不阻塞
return "Task " + i + " done";
}))
.toList();
futures.forEach(f -> {
try { System.out.println(f.get()); }
catch (Exception e) { e.printStackTrace(); }
});
}
// 1000 个虚拟线程,0.1 秒全部完成!
3. Spring Boot 集成虚拟线程
# application.properties
spring.threads.virtual.enabled=true # Spring Boot 3.2+
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
4. CompletableFuture 进阶
// 组合多个异步调用
CompletableFuture<String> userFuture =
CompletableFuture.supplyAsync(() -> fetchUser(id), virtualExecutor);
CompletableFuture<Integer> orderFuture =
CompletableFuture.supplyAsync(() -> fetchOrders(id), virtualExecutor);
String result = CompletableFuture
.allOf(userFuture, orderFuture)
.thenApply(v -> String.format(
"用户: %s, 订单数: %d",
userFuture.join(), orderFuture.join()
))
.exceptionally(ex -> "Error: " + ex.getMessage())
.join();
5. 线程安全的集合
// ❌ 线程不安全
Map<String, Integer> map = new HashMap<>();
// ✅ ConcurrentHashMap
Map<String, Integer> safeMap = new ConcurrentHashMap<>();
// ✅ CopyOnWriteArrayList — 读多写少场景
List<String> list = new CopyOnWriteArrayList<>();
// ✅ ConcurrentLinkedQueue — 高并发队列
Queue<String> queue = new ConcurrentLinkedQueue<>();
虚拟线程最佳实践
| 场景 | 建议 |
|---|---|
| I/O 密集型(HTTP/DB 调用) | 虚拟线程极佳 |
| CPU 密集型 | 用平台线程 + ForkJoinPool |
| 使用 synchronized | 会 pin 住虚拟线程,优先用 ReentrantLock |
| ThreadLocal | 避免大量使用,改用 ScopedValue(JDK 21 preview) |
| 连接池 | 虚拟线程减少连接池压力,但数据库连接池仍需限制 |
思考题
- 虚拟线程为什么不适合 CPU 密集型任务?在什么条件下会 pin 住底层平台线程?
ReentrantLock和synchronized在虚拟线程下的行为有什么区别?- 如果数据库连接池只有 10 个连接,启动 10000 个虚拟线程同时查询会怎样?