并发模型的彻底转向
Java 21 引入虚拟线程后,Java 的并发编程范式发生了根本变化:一个请求一个线程的写法重新变得可行,而且不再需要为了吞吐去写反人类的异步回调链。
Java 25 在这一方向上继续收敛:虚拟线程的性能与可观测性更好,配套的作用域值(Scoped Values)与结构化并发(Structured Concurrency)进入更成熟的阶段,同时语言层面的模式匹配也更加完整。
虚拟线程:便宜到可以随便创建
import java.util.concurrent.Executors;
import java.time.Duration;
public class VirtualThreadsDemo {
public static void main(String[] args) throws Exception {
// 每个任务一个虚拟线程,无需池化
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
int id = i;
executor.submit(() -> {
Thread.sleep(Duration.ofMillis(100)); // 阻塞时自动卸载,不占 OS 线程
return fetchUser(id);
});
}
} // 自动等待全部完成
}
static String fetchUser(int id) {
return "user-" + id;
}
}关键认知:
- 不要池化虚拟线程——它们不是稀缺资源;
synchronized的 pinning 问题已在 JDK 24 修复(JEP 491),不再会拖住载体线程;- CPU 密集任务仍用平台线程池,虚拟线程只对阻塞型 IO 有效。
Scoped Values:替代 ThreadLocal 的正确方式
import java.lang.ScopedValue;
public class RequestContext {
static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();
static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
public Response handle(Request req) {
// 绑定后,当前线程及所有子任务的生命周期内可见
return ScopedValue.where(TRACE_ID, req.traceId())
.where(CURRENT_USER, authenticate(req))
.call(() -> process(req));
}
Response process(Request req) {
// 任意深度调用栈都能直接读取,无需层层传参
log.info("trace={}, user={}", TRACE_ID.get(), CURRENT_USER.get().name());
try (var scope = StructuredTaskScope.open()) {
var orders = scope.fork(() -> orderService.list(CURRENT_USER.get()));
var coupons = scope.fork(() -> couponService.list(CURRENT_USER.get()));
scope.join();
return new Response(orders.get(), coupons.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}相比 ThreadLocal:作用域值不可变、有明确的生命周期边界、在虚拟线程大规模创建时开销远低于可变 ThreadLocal。
结构化并发:让并发任务有父子关系
import java.util.concurrent.StructuredTaskScope;
record Product(Stock stock, Price price) {}
Product loadProduct(String sku) throws Exception {
// 任一子任务失败即整体取消,不会遗留孤儿任务
try (var scope = StructuredTaskScope.open(
StructuredTaskScope.Joiner.<Stock>anySuccessfulResultOrThrow())) {
scope.fork(() -> stockClient.get(sku));
scope.fork(() -> priceClient.get(sku));
return scope.join();
}
}结构化并发解决的是“取消传播”问题:以前 ExecutorService 里的任务即使主线程超时返回,后台线程仍在跑;现在子任务的生命周期被严格限制在 try 块内。
语言层面的顺手改进
// 1. 模式匹配 + record 解构
sealed interface Shape permits Circle, Rect {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Rect(double w, double h) -> w * h;
};
}
// 2. 灵活的构造函数体:super() 之前可以先做校验
class PositivePoint extends Point {
PositivePoint(int x, int y) {
if (x <= 0 || y <= 0) throw new IllegalArgumentException();
super(x, y);
}
}
// 3. 紧凑的源文件(隐式类 + 自动导入)
void main() {
IO.println("Hello, Java 25");
}升级建议
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>- 把
newFixedThreadPool换成newVirtualThreadPerTaskExecutor,先在数据访问层试点; - 用
ScopedValue替换跨层传递的ThreadLocal,尤其是链路追踪上下文; - 开启
-XX:+UseCompactObjectHeaders与 Generational ZGC,进一步提升吞吐。
小结
Java 正在把“写高并发服务”的门槛降回到写同步代码的水平:虚拟线程负责吞吐,作用域值负责上下文,结构化并发负责生命周期。这是近十年 Java 最重要的一次体验升级,值得每个后端团队认真评估。
评论 0
还没有评论,来说两句吧。