本文目录导读:

Java 并发编程领域在 JDK 8 到 JDK 21 之间经历了一些非常重要的演进,但最核心、最具革命性的变化是 Project Loom 带来的虚拟线程,它在 JDK 21 正式转正为 GA(General Availability,通用可用性)版本。
以下是目前 Java 并发编程领域值得关注的新工具和机制:
虚拟线程
这是目前最重要、最应该掌握的新工具,它彻底改变了“高并发”的实现方式。
-
是什么: 不再是操作系统级的线程(平台线程),而是由 JVM 管理的轻量级线程,你可以在一个平台线程上创建数以万计甚至百万计的虚拟线程。
-
为什么重要: 传统 Thread 是重量级资源,主要靠“异步编程”和“回调”来压榨性能(如 CompletableFuture、WebFlux),虚拟线程的目标是让代码回归“同步 + 阻塞”的简单写法,同时获得极高的并发吞吐量。
-
最佳实践: 每个业务请求直接创建一个新虚拟线程,不用线程池。
// 直接创建启动 Thread.startVirtualThread(() -> { // 这里可以放同步阻塞的IO操作(如数据库查询、RPC调用) System.out.println("Hello from virtual thread!"); }); // 使用Executors创建作用域(推荐) try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() -> { Thread.sleep(1000); // 阻塞不会浪费OS线程 return "done"; }); }
结构化并发
这是与虚拟线程配套出现的一种编程模型,目的是解决任务生命周期管理和错误传播的问题。
-
是什么: 它将多个并发任务绑定在同一个代码作用域内,当一个作用域结束时,其内部的所有并发子任务都必须完成或被取消,这避免了“任务泄露”和“悬空线程”。
-
核心类:
StructuredTaskScope。 -
作用:
- 当你开启多个并发任务(同时查询用户信息和订单信息),如果任何一个任务失败,结构化并发可以自动取消其他未完成的任务,而不是让它们无意义地继续执行。
- 替代传统
ExecutorService+CountDownLatch/Future的复杂组合。
-
示例:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { // 启动两个并发任务 Future<String> user = scope.fork(() -> fetchUser()); Future<Integer> order = scope.fork(() -> fetchOrderCount()); // 等待所有任务完成,一旦失败立即取消剩余任务 scope.join(); scope.throwIfFailed(); // 合并结果,主线程在此继续 return user.resultNow() + " has " + order.resultNow() + " orders"; } // 作用域结束,所有任务自动清理
Scoped Values(作用域值)
这是一个替代 ThreadLocal 的新工具,专为虚拟线程设计。
-
是什么: 允许在一个线程(及其子线程/虚拟线程)的作用域内,绑定一个不可变的值。
-
为什么替代 ThreadLocal:
- 虚拟线程数量巨大,使用
ThreadLocal会导致内存开销剧增(因为每个虚拟线程都需要维护一份变量副本)。 - 传统
ThreadLocal的继承机制(InheritableThreadLocal)在虚拟线程下不工作(因为虚拟线程不是简单地继承自父线程)。 ScopedValue的写入和读取有明确的作用域边界,不会逃脱到其他不相关的线程。
- 虚拟线程数量巨大,使用
-
核心用法:
ScopedValue.where(key, value).run(callback)。public static final ScopedValue<String> LOGGED_IN_USER = ScopedValue.newInstance(); void processRequest() { // 在作用域内绑定值,作用域内执行的代码都能通过 get() 获取 ScopedValue.where(LOGGED_IN_USER, "Alice") .run(() -> { // 任何在此作用域内启动的虚拟线程都能读到该值 Thread.startVirtualThread(() -> { System.out.println("User: " + LOGGED_IN_USER.get()); // 输出 "Alice" }); }); // 离开作用域后,get() 会抛出异常或返回默认值 }
传统工具的增强(JDK 8+)
除了上述“新”工具,一些已有工具在 JDK 8+ 也得到增强,但需要留意:
- CompletableFuture: 仍然是处理非阻塞、无返回结果的异步任务的主流,它没有因为虚拟线程的出现而被淘汰,反而用于跨虚拟线程协调(将多个虚拟线程的结果组合成一个
CompletableFuture)。 - 并发集合(ConcurrentHashMap, ConcurrentLinkedQueue): 针对虚拟线程的性能进行了优化(比如更细粒度的锁或无锁算法)。
- ReentrantLock: 引入了条件变量和可中断锁的支持,并且被虚拟线程的调度器(ForkJoinPool)特殊优化,当虚拟线程在 ReentrantLock 上阻塞时,会释放底层的平台线程去运行其他虚拟线程,这比 synchronized 关键字更高效(synchronized 在虚拟线程中会“钉住”平台线程)。
该用哪个?
| 场景 | 现代推荐方式 (JDK 21+) | 传统方式 (JDK 8-11) | 理由 |
|---|---|---|---|
| 高并发,IO密集型 | 虚拟线程 + StructuredTaskScope |
CompletableFuture + 线程池 |
代码简单,吞吐量高,无需改写成异步回调 |
| 需要线程本地数据(如用户上下文) | ScopedValue |
ThreadLocal |
更安全、更轻量,兼容虚拟线程 |
| 任务编排,超时控制 | StructuredTaskScope |
CompletableFuture.orTimeout() + allOf() |
结构化,错误传播清晰,自动取消 |
| CPU密集型计算 | 还是用 平台线程(线程池) |
平台线程池 + RecursiveTask |
虚拟线程对于 CPU 密集型计算无明显优势(因为它们不会阻塞释放平台线程) |
| 任务结果同步组合 | CompletableFuture + 虚拟线程 |
CompletableFuture |
虚拟线程执行异步操作,CompletionStage 仍用于组合结果 |
核心建议:
如果你正在使用 JDK 21 或更高版本,新项目应该优先使用虚拟线程 + 结构化并发。ScopedValue 替代 ThreadLocal。synchronized 尽量改为 ReentrantLock(避免钉住平台线程)。
如果你还在用 JDK 11 或 8,则 CompletableFuture + ForkJoinPool 仍然是最主流、最有效的并发方案。