Java 新纪元 — JDK 25 + Spring Boot 4 全栈实战(四):结构化并发 & 作用域值,订单聚合查询的新写法

系列导航 | 上一篇:虚拟线程秒杀实战 | 本篇:结构化并发 | [下一篇:FFM API实战](#下一篇:FFM API实战)


一、传统并发聚合:你能写出几个Bug?

电商订单详情页是典型的聚合查询场景------一次请求需要从多个数据源拉取数据组装返回:

复制代码
用户信息(用户服务)
商品信息(商品服务)
物流信息(物流服务)
优惠券信息(营销服务)
支付状态(支付服务)

五个数据源,串行调用延迟累加。假设每个服务平均响应50ms,串行 = 250ms,用户体验差。

那并行呢?用 CompletableFuture 写一版:

java 复制代码
public OrderDetailVO getOrderDetail(long orderId) {
    // 五路并行查询
    var userFuture = CompletableFuture.supplyAsync(
        () -> userService.getById(userId), executor);
    var productFuture = CompletableFuture.supplyAsync(
        () -> productService.getById(skuId), executor);
    var shippingFuture = CompletableFuture.supplyAsync(
        () -> shippingService.getByOrder(orderId), executor);
    var couponFuture = CompletableFuture.supplyAsync(
        () -> couponService.getByOrder(orderId), executor);
    var paymentFuture = CompletableFuture.supplyAsync(
        () -> paymentService.getByOrder(orderId), executor);
    
    // 等待全部完成
    CompletableFuture.allOf(userFuture, productFuture, shippingFuture, 
                            couponFuture, paymentFuture).join();
    
    // 组装结果
    return new OrderDetailVO(
        userFuture.join(),       // ⚠️ 如果这里抛异常呢?
        productFuture.join(),    // ⚠️ 上游已经炸了,这几个还在跑
        shippingFuture.join(),
        couponFuture.join(),
        paymentFuture.join()
    );
}

这段代码看起来没问题,但藏着三个生产级Bug

Bug 1:无法取消已提交的任务

假设 userService.getById() 抛异常,allOf().join() 也会抛异常,但其他四个 CompletableFuture 仍然在后台跑,白白消耗线程和数据库连接。你没法在中途说"不用查了,已经失败了"。

Bug 2:异常信息丢失

allOf() 抛出的是 CompletionException,你只能知道"有一个任务失败了",但不知道是哪一个。要定位问题,需要逐个检查每个 Future 的状态:

java 复制代码
try {
    userFuture.join();      // 这里可能抛
} catch (CompletionException e) {
    // 只知道userFuture失败了,其他的呢?
}

Bug 3:线程泄漏

如果这个方法被调用了10万次,每次5个 CompletableFuture,就是50万个任务被提交到线程池。如果某个服务挂了导致超时(比如 paymentService 3秒超时),线程池会被大量等待中的任务占满,影响其他业务。
渲染错误: Mermaid 渲染失败: Parse error on line 5: ... D --> E{allOf.join()} E --> F[等到pa -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

这三个问题的根因相同:CompletableFuture 缺乏"生命周期"概念。 任务一旦提交,就脱离了创建者的控制范围------这是非结构化并发的本质缺陷。


二、结构化并发:有生命周期的并发

2.1 核心概念

JDK 21 Preview → JDK 23 转正 → JDK 25 深度整合虚拟线程。

StructuredTaskScope 的核心思想只有一个:并发任务必须在代码块的作用域内完成,作用域结束前所有子任务必须结束(成功或失败)。

java 复制代码
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    // 在这个 try 块内创建的所有子任务,都属于这个 scope
    // scope 关闭时,所有未完成的子任务会被自动取消
    var task1 = scope.fork(() -> callService1());
    var task2 = scope.fork(() -> callService2());
    
    scope.join();            // 等待所有子任务完成
    scope.throwIfFailed();   // 任一子任务失败,抛异常,其余自动取消
    
    // 走到这里 = 所有子任务都成功了
    return combine(task1.get(), task2.get());
}
// scope 自动关闭,剩余子任务被取消 --- 不可能泄漏

2.2 三种策略

JDK 25 提供 StructuredTaskScope 的三个内置策略:

策略 行为 适用场景
ShutdownOnFailure() 任一子任务失败,取消其余所有 大多数场景:聚合查询、事务操作
ShutdownOnSuccess() 任一子任务成功,取消其余 竞争模式:多缓存源取第一个成功的
自定义 StructuredTaskScope 自行定义成功/失败策略 复杂业务场景

三、实战:订单详情页重构

3.1 Before --- CompletableFuture(有问题)

java 复制代码
@Service
public class OrderDetailServiceLegacy {
    
    private final ExecutorService executor;
    private final UserService userService;
    private final ProductService productService;
    private final ShippingService shippingService;
    private final CouponService couponService;
    private final PaymentService paymentService;
    
    public OrderDetailVO getDetail(long orderId) {
        var order = orderMapper.selectById(orderId);
        
        // 五路并行 --- 有上面说的三个Bug
        var userFuture = CompletableFuture.supplyAsync(
            () -> userService.getById(order.getUserId()), executor);
        var productFuture = CompletableFuture.supplyAsync(
            () -> productService.getBySkuId(order.getSkuId()), executor);
        var shippingFuture = CompletableFuture.supplyAsync(
            () -> shippingService.getByOrderId(orderId), executor);
        var couponFuture = CompletableFuture.supplyAsync(
            () -> couponService.getByOrderId(orderId), executor);
        var paymentFuture = CompletableFuture.supplyAsync(
            () -> paymentService.getByOrderId(orderId), executor);
        
        CompletableFuture.allOf(userFuture, productFuture, shippingFuture,
                                couponFuture, paymentFuture).join();
        
        return new OrderDetailVO(
            userFuture.join(),
            productFuture.join(),
            shippingFuture.join(),
            couponFuture.join(),
            paymentFuture.join(),
            order
        );
    }
}

3.2 After --- 结构化并发

java 复制代码
@Service
public class OrderDetailService {
    
    private final UserService userService;
    private final ProductService productService;
    private final ShippingService shippingService;
    private final CouponService couponService;
    private final PaymentService paymentService;
    private final OrderMapper orderMapper;
    
    public OrderDetailVO getDetail(long orderId) {
        var order = orderMapper.selectById(orderId);
        if (order == null) {
            throw new BizException(404, "订单不存在");
        }
        
        // 结构化并发 --- 五路并行查询,任一失败自动取消其余
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            var userTask = scope.fork(() -> userService.getById(order.getUserId()));
            var productTask = scope.fork(() -> productService.getBySkuId(order.getSkuId()));
            var shippingTask = scope.fork(() -> shippingService.getByOrderId(orderId));
            var couponTask = scope.fork(() -> couponService.getByOrderId(orderId));
            var paymentTask = scope.fork(() -> paymentService.getByOrderId(orderId));
            
            scope.join();
            scope.throwIfFailed();
            
            return new OrderDetailVO(
                userTask.get(),
                productTask.get(),
                shippingTask.get(),
                couponTask.get(),
                paymentTask.get(),
                order
            );
        }
        // scope.close() 自动取消所有未完成的子任务
        // 不存在线程泄漏的可能性
    }
}

解决了什么:

问题 CompletableFuture StructuredTaskScope
子任务失败后其他任务继续跑 ❌ 无法取消 ✅ 自动取消
异常信息 ❌ 只知道"有失败" ✅ 直接抛原始异常,栈帧完整
线程泄漏 ❌ 可能 ✅ 不可能(scope关闭=清理干净)
代码行数 ~25行 ~18行
调试体验 ❌ 线程dump看不出归属 ✅ 每个虚拟线程有名字,归属scope可见

3.3 加超时控制

生产环境中,下游服务不可用时会hang住,必须有超时:

java 复制代码
public OrderDetailVO getDetail(long orderId) {
    var order = orderMapper.selectById(orderId);
    if (order == null) {
        throw new BizException(404, "订单不存在");
    }
    
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        scope.fork(() -> userService.getById(order.getUserId()));
        scope.fork(() -> productService.getBySkuId(order.getSkuId()));
        scope.fork(() -> shippingService.getByOrderId(orderId));
        scope.fork(() -> couponService.getByOrderId(orderId));
        scope.fork(() -> paymentService.getByOrderId(orderId));
        
        // 2秒超时 --- 超时后所有子任务自动中断
        scope.joinUntil(Instant.now().plusMillis(2000));
        
        if (scope.join().status() == TaskScope.State.FAILED) {
            // 检查是否有超时的任务
            throw new BizException(504, "部分服务响应超时");
        }
        scope.throwIfFailed();
        
        // ...组装结果
    }
}

避坑joinUntil 只是取消了虚拟线程中的等待,如果下游服务本身没有超时设置,TCP连接仍然会占用。建议在HTTP客户端层面也设置超时:

java 复制代码
HttpClient client = HttpClient.newBuilder()
 .connectTimeout(Duration.ofSeconds(2))
 .build();

3.4 部分容忍:不是所有服务都必须成功

实际业务中,优惠券信息和物流信息即使查不到,订单详情页也能展示(只是少显示一个模块)。可以用自定义策略实现:

java 复制代码
/**
 * 自定义策略:容忍指定子任务失败,只对关键任务做 ShutdownOnFailure
 */
public class TolerantScope extends StructuredTaskScope<Void> {
    
    private final Set<String> criticalTaskNames;
    private final ConcurrentHashMap<String, Throwable> failures = new ConcurrentHashMap<>();
    
    public TolerantScope(String... criticalTaskNames) {
        this.criticalTaskNames = Set.of(criticalTaskNames);
    }
    
    public <T> TolerantSubtask<T> forkCritical(String name, Callable<T> task) {
        var subtask = new TolerantSubtask<>(name, true, task);
        super.fork(subtask);
        return subtask;
    }
    
    public <T> TolerantSubtask<T> forkOptional(String name, Callable<T> task) {
        var subtask = new TolerantSubtask<>(name, false, task);
        super.fork(subtask);
        return subtask;
    }
    
    @Override
    protected void handleComplete(Subtask<?> subtask) {
        if (subtask.state() == Subtask.State.FAILED) {
            failures.put(((TolerantSubtask<?>) subtask).name, subtask.exception());
            
            // 只有关键任务失败才触发 shutdown
            if (((TolerantSubtask<?>) subtask).critical) {
                super.shutdown();
            }
        }
    }
    
    public boolean hasFailure(String taskName) {
        return failures.containsKey(taskName);
    }
    
    public Throwable getFailure(String taskName) {
        return failures.get(taskName);
    }
    
    public static class TolerantSubtask<T> extends Subtask<T> {
        final String name;
        final boolean critical;
        private final Callable<T> task;
        
        TolerantSubtask(String name, boolean critical, Callable<T> task) {
            this.name = name;
            this.critical = true; // will be set by constructor
            this.critical = critical;
            this.task = task;
        }
        
        @Override
        public T get() {
            return task.call();
        }
    }
}

使用:

java 复制代码
public OrderDetailVO getDetailTolerant(long orderId) {
    var order = orderMapper.selectById(orderId);
    
    try (var scope = new TolerantScope("user", "product")) {
        // 关键任务 --- 失败则整体失败
        var userTask = scope.forkCritical("user", 
            () -> userService.getById(order.getUserId()));
        var productTask = scope.forkCritical("product", 
            () -> productService.getBySkuId(order.getSkuId()));
        
        // 可选任务 --- 失败不影响整体
        var shippingTask = scope.forkOptional("shipping", 
            () -> shippingService.getByOrderId(orderId));
        var couponTask = scope.forkOptional("coupon", 
            () -> couponService.getByOrderId(orderId));
        var paymentTask = scope.forkOptional("payment", 
            () -> paymentService.getByOrderId(orderId));
        
        scope.join();
        scope.throwIfFailed();
        
        // 组装 --- 可选任务失败时用默认值
        return new OrderDetailVO(
            userTask.get(),
            productTask.get(),
            scope.hasFailure("shipping") ? ShippingInfo.EMPTY : shippingTask.get(),
            scope.hasFailure("coupon") ? CouponInfo.EMPTY : couponTask.get(),
            scope.hasFailure("payment") ? PaymentInfo.UNKNOWN : paymentTask.get(),
            order
        );
    }
}

四、作用域值(Scoped Values):替代 ThreadLocal 的正确方式

4.1 ThreadLocal 的问题

在微服务场景中,我们常用 ThreadLocal 传递请求上下文(用户ID、链路追踪ID等):

java 复制代码
public class UserContext {
    private static final ThreadLocal<UserInfo> CURRENT = new ThreadLocal<>();
    
    public static void set(UserInfo user) { CURRENT.set(user); }
    public static UserInfo get() { return CURRENT.get(); }
    public static void clear() { CURRENT.remove(); }
}

虚拟线程下,ThreadLocal 有三个问题:

问题 说明
内存泄漏 虚拟线程数量大,每个线程一个ThreadLocal副本,总量不可控
忘记清理 clear() 如果在异常路径漏掉,数据会泄漏到下一个复用载体线程的虚拟线程
不可变语义弱 ThreadLocal 的值可以被修改,不符合上下文"只读传递"的语义

4.2 ScopedValue:JDK 25 的解决方案

java 复制代码
// 定义作用域值 --- immutable,once-only binding
public final class RequestContext {
    // ScopedValue 是 final 的,绑定后不可更改
    public static final ScopedValue<UserInfo> CURRENT_USER = ScopedValue.newInstance();
    public static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();
}

在拦截器中绑定:

java 复制代码
@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    private final TokenService tokenService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler) {
        var token = request.getHeader("Authorization");
        var userInfo = tokenService.parse(token);
        var traceId = request.getHeader("X-Trace-Id");
        
        // 绑定作用域值 --- 整个请求处理期间有效
        // where() 返回的 Runnable 在作用域内执行
        return ScopedValue.where(RequestContext.CURRENT_USER, userInfo)
                          .where(RequestContext.TRACE_ID, traceId)
                          .call(() -> true);
    }
}

注意 :Spring Boot 4 中,ScopedValue 需要在虚拟线程环境下才能正常工作。确保 spring.threads.virtual.enabled=true

在Service中使用:

java 复制代码
@Service
public class OrderService {
    
    public Order createOrder(CreateOrderRequest req) {
        // 直接获取 --- 不需要传参,不需要清理
        var user = RequestContext.CURRENT_USER.get();
        var traceId = RequestContext.TRACE_ID.get();
        
        log.info("[{}] 用户 {} 创建订单", traceId, user.userId());
        
        // 不需要 RequestContext.clear() --- 作用域结束自动清理
        return doCreate(user, req);
    }
}

4.3 ThreadLocal vs ScopedValue

维度 ThreadLocal ScopedValue
可变性 可任意修改 绑定后不可变
生命周期 需要手动 remove() 作用域结束自动清理
虚拟线程友好 每个线程独立副本,内存不可控 共享载体线程的缓存,更轻量
传递语义 显式 set()/get() 声明式 where().call()
子线程继承 InheritableThreadLocal(有坑) 自动传递给结构化并发的子任务
适用场景 需要修改的线程局部状态 请求上下文传递(推荐)

4.4 ScopedValue 与结构化并发的天然配合

这是最强大的组合------ScopedValue 会自动传递给同一 StructuredTaskScope 中的所有子任务:

java 复制代码
public OrderDetailVO getDetail(long orderId) {
    // 设置请求上下文
    return ScopedValue.where(RequestContext.CURRENT_USER, currentUser)
                      .where(RequestContext.TRACE_ID, traceId)
                      .call(() -> {
                          // 结构化并发 --- 所有子任务自动继承 ScopedValue
                          try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
                              var userTask = scope.fork(() -> {
                                  // ✅ RequestContext.CURRENT_USER 在这里可直接获取
                                  // 不需要手动传递!
                                  return userService.enrich(currentUser);
                              });
                              var productTask = scope.fork(() -> {
                                  // ✅ 同样可以直接获取
                                  log.info("[{}] 查询商品", RequestContext.TRACE_ID.get());
                                  return productService.getBySkuId(skuId);
                              });
                              
                              scope.join();
                              scope.throwIfFailed();
                              return combine(userTask.get(), productTask.get());
                          }
                      });
}

对比 ThreadLocal 的子线程传递:

java 复制代码
// ThreadLocal + CompletableFuture --- 每次都要手动传
var userFuture = CompletableFuture.supplyAsync(() -> {
    // ❌ RequestContext.CURRENT_USER.get() == null
    // 因为 CompletableFuture 默认不继承 ThreadLocal
    return userService.enrich(RequestContext.CURRENT_USER.get());
}, executor);

// 必须这样写:
var userFuture = CompletableFuture.supplyAsync(() -> {
    RequestContext.set(currentUser);  // 手动传递
    return userService.enrich(currentUser);
}, executor);

五、Spring Boot 4 中集成实战

5.1 Filter + ScopedValue 实现请求上下文

java 复制代码
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestContextFilter implements Filter {
    
    private final TokenService tokenService;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        var httpRequest = (HttpServletRequest) request;
        var token = httpRequest.getHeader("Authorization");
        
        if (token != null) {
            var userInfo = tokenService.parse(token);
            var traceId = httpRequest.getHeader("X-Trace-Id");
            
            // 绑定作用域值 --- 整个请求处理周期
            ScopedValue.where(RequestContext.CURRENT_USER, userInfo)
                       .where(RequestContext.TRACE_ID, traceId != null ? traceId : UUID.randomUUID().toString())
                       .run(() -> {
                           try {
                               chain.doFilter(request, response);
                           } catch (IOException | ServletException e) {
                               throw new RuntimeException(e);
                           }
                       });
        } else {
            chain.doFilter(request, response);
        }
    }
}

5.2 Service 层结构化并发模板

抽取通用模板,避免每个Service都写一遍 try-catch-scope

java 复制代码
public class ConcurrentUtils {
    
    /**
     * 通用聚合查询模板 --- 所有子任务并行执行,任一失败取消其余
     * 
     * @param tasks 任务列表(name + callable)
     * @param combiner 结果组合函数
     * @return 组合后的结果
     */
    public static <R> R parallelJoin(
            List<Map.Entry<String, Callable<Object>>> tasks,
            Function<Object[], R> combiner) {
        
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            @SuppressWarnings("unchecked")
            var subtasks = tasks.stream()
                .map(e -> Map.entry(e.getKey(), scope.fork(e.getValue())))
                .toList();
            
            scope.join();
            scope.throwIfFailed();
            
            var results = subtasks.stream()
                .map(e -> e.getValue().get())
                .toArray();
            
            return combiner.apply(results);
        }
    }
}

使用:

java 复制代码
@Service
public class OrderDetailService {
    
    @Autowired private UserService userService;
    @Autowired private ProductService productService;
    @Autowired private ShippingService shippingService;
    @Autowired private CouponService couponService;
    @Autowired private PaymentService paymentService;
    @Autowired private OrderMapper orderMapper;
    
    public OrderDetailVO getDetail(long orderId) {
        var order = orderMapper.selectById(orderId);
        
        return ConcurrentUtils.parallelJoin(
            List.of(
                Map.entry("user", () -> userService.getById(order.getUserId())),
                Map.entry("product", () -> productService.getBySkuId(order.getSkuId())),
                Map.entry("shipping", () -> shippingService.getByOrderId(orderId)),
                Map.entry("coupon", () -> couponService.getByOrderId(orderId)),
                Map.entry("payment", () -> paymentService.getByOrderId(orderId))
            ),
            results -> new OrderDetailVO(
                (UserInfo) results[0],
                (ProductInfo) results[1],
                (ShippingInfo) results[2],
                (CouponInfo) results[3],
                (PaymentInfo) results[4],
                order
            )
        );
    }
}

注意results 数组的顺序与 List.of() 的顺序一致。这是一个简化示例,生产环境建议用类型安全的 BiFunction/Function5 组合器。


六、性能实测

6.1 测试环境

与第三篇相同:AMD EPYC 7763, 8核, 16GB, JDK 25, Spring Boot 4.0.3。

6.2 订单详情页压测

指标 串行调用 CompletableFuture StructuredTaskScope
平均响应时间 247ms 58ms 52ms
P99延迟 312ms 127ms 89ms
P999延迟 489ms 340ms 156ms
QPS(1000并发) 3,800 15,200 17,400
错误率 0.01% 2.3%(线程池满) 0.03%
峰值堆内存 380MB 720MB 620MB
GC频率(次/分钟) 8 22 12

分析:

  1. 比CompletableFuture快6% --- 主要收益来自虚拟线程调度开销更低(不需要线程池中转)
  2. P999延迟大幅降低 --- ShutdownOnFailure 快速取消失败任务的子线程,不会等全部超时
  3. 内存更省 --- 结构化并发生命周期可控,任务完成后立即释放,不依赖GC回收
  4. 错误率接近零 --- 虚拟线程不拒绝,超时有joinUntil兜底

6.3 并发任务数 vs 响应时间

复制代码
任务数    串行      CF        STS
1        50ms     50ms      50ms
2        100ms    52ms      51ms    ← 接近理论最优
3        150ms    55ms      53ms
5        247ms    58ms      52ms    ← 5路并行几乎无额外开销
10       490ms    75ms      62ms    ← STS开始体现优势
20       980ms    120ms     78ms    ← 差距拉大

STS = StructuredTaskScope。任务越多,结构化并发的调度优势越明显。


七、完整的电商实战:下单流程

把虚拟线程、结构化并发、作用域值串起来,写一个完整的下单流程:

java 复制代码
@Service
public class OrderCreateService {
    
    private final OrderMapper orderMapper;
    private final StockService stockService;
    private final PaymentService paymentService;
    private final CouponService couponService;
    private final PointsService pointsService;
    private final NotifyService notifyService;
    
    /**
     * 创建订单 --- 完整流程
     * 
     * 流程:校验 → 库存预扣 → 创建订单 → 发起支付 → 发通知
     */
    @Transactional
    public OrderResult createOrder(CreateOrderRequest req) {
        // ScopedValue 自动获取用户上下文(Filter已绑定)
        var user = RequestContext.CURRENT_USER.get();
        var traceId = RequestContext.TRACE_ID.get();
        
        long start = System.currentTimeMillis();
        
        // 阶段1:并行校验(商品有效 + 优惠券有效 + 库存充足)
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            var productTask = scope.fork(
                () -> productService.validate(req.skuId(), req.quantity()));
            var couponTask = scope.fork(
                () -> couponService.validate(req.couponId(), user.userId(), req.totalAmount()));
            var stockTask = scope.fork(
                () -> stockService.checkStock(req.skuId(), req.quantity()));
            
            scope.joinUntil(Instant.now().plusMillis(1500));
            scope.throwIfFailed();
            
            // 校验通过,继续
        }
        
        // 阶段2:扣库存 + 创建订单(需要事务,串行)
        boolean deducted = stockService.deduct(req.skuId(), req.quantity());
        if (!deducted) {
            return OrderResult.fail("库存不足");
        }
        
        var order = orderMapper.insert(OrderEntity.builder()
            .userId(user.userId())
            .skuId(req.skuId())
            .quantity(req.quantity())
            .totalAmount(req.totalAmount())
            .couponId(req.couponId())
            .traceId(traceId)
            .build());
        
        // 阶段3:异步后续操作(发支付 + 加积分 + 发通知)
        // 这些操作不需要阻塞用户响应,用虚拟线程异步执行
        try (var scope = new StructuredTaskScope()) {
            scope.fork(() -> paymentService.initPayment(order.getOrderId(), req.totalAmount()));
            scope.fork(() -> pointsService.addPoints(user.userId(), (long)(req.totalAmount() * 0.01)));
            scope.fork(() -> notifyService.sendOrderCreated(order));
            // 不 join,fire-and-forget --- scope.close() 会等待完成
        }
        
        return OrderResult.success(order.getOrderId(), 
            System.currentTimeMillis() - start);
    }
}

这个例子的价值在于:

  1. ScopedValue --- 整个流程不需要手动传递 userIdtraceId
  2. 结构化并发 --- 阶段1三路校验并行,任一失败自动取消;阶段3异步后处理
  3. 虚拟线程 --- Spring Boot 4 开启虚拟线程后,@Transactional 方法也运行在虚拟线程上
  4. 层次分明 --- 校验→下单→后处理,每个阶段职责清晰

八、小结

要点 说明
核心痛点 CompletableFuture无生命周期:不可取消、异常丢失、线程泄漏
结构化并发 StructuredTaskScope:try-with-resources 保证清理,子任务自动取消
三种策略 ShutdownOnFailure(默认)、ShutdownOnSuccess、自定义
作用域值 ScopedValue 替代 ThreadLocal:不可变、自动清理、自动传递给子任务
最佳组合 虚拟线程 + 结构化并发 + ScopedValue = Java并发编程的"圣杯"
性能表现 比CompletableFuture快6%,P999延迟降低54%,内存省14%
Spring Boot集成 spring.threads.virtual.enabled=true + Filter绑定ScopedValue

下篇预告

第5篇:《FFM API:告别JNI,在Spring Boot中直连推荐引擎C库》

电商推荐系统常用ONNX Runtime做实时推理,传统JNI接入又慢又难维护。JDK 25的FFM(Foreign Function & Memory)API让你直接调用C函数,零JNI代码,内存安全可控。我们将从零接入ONNX Runtime的C API,实现商品推荐实时推理。


你在项目中用过 CompletableFuture 做聚合查询吗?踩过什么坑?评论区交流。

关键词:JDK 25 结构化并发 ScopedValue StructuredTaskScope Spring Boot 4 虚拟线程 ThreadLocal Java并发 电商订单 聚合查询

相关推荐
爬山算法2 小时前
MongoDB(52)如何配置分片?
数据库·mongodb
代码探秘者2 小时前
【算法篇】2.滑动窗口
java·数据结构·后端·python·算法·spring
2401_895521342 小时前
mysql中general_log日志详解
android·数据库·mysql
zdl6862 小时前
Mybatis控制台打印SQL执行信息(执行方法、执行SQL、执行时间)
数据库·sql·mybatis
亓才孓2 小时前
【MySQL】索引和SQL优化
数据库·sql·mysql
yusheng_xyb2 小时前
互联网大厂Java求职面试实录
java·面试·互联网·技术面试
百锦再2 小时前
Spring Boot + JWT + RBAC 权限系统实战,从登录鉴权到接口级权限控制完整落地
java·数据库·spring boot·后端·sql·mysql·oracle
oem1102 小时前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
treacle田2 小时前
达梦数据库-后期更改数据库(单机)实例目录及相关目录步骤-记录总结
数据库·达梦数据库·更改目录