JEP 525 为 Java 的结构化并发引入超时处理与连接器优化

  • 2026-01-07
    北京
  • 本文字数:1818 字

    阅读完需:约 6 分钟

JEP 525(结构化并发预览版6)已经完成,将随即将发布的JDK 26交付。自 JDK 21 发布以来,结构化并发 API 已经经历过多轮预览改进,JEP 525 延续了这项工作,目的是使并发任务管理比传统的 ExecutorService/Future 模式更清晰、更安全、更易于推理。这次迭代只做了一些小幅但有用的调整,核心模型保持不变。

 

结构化并发将相关任务组视为一个工作单元,与临时线程管理相比,改善了任务取消、错误传播和可观察性。该 API 以 java.util.concurrent.StructuredTaskScope和 Joiner 抽象为中心,后者可以控制如何以及何时组合分叉子任务的结果。

 

与预览版 5(用静态工厂方法替换构造函数并引入更丰富的Joiner策略)不同,预览版 6 专注于对 API 进行一系列增量式的小改进。

 

结构化并发作用域可以配置超时。在此次预览之前,scope.join()超时时会立即抛出 TimeoutException 异常。预览版 6 在 Joiner 上引入了新的 onTimeout()回调,允许自定义连接器响应超时,并返回部分或后备结果,而不是总是抛出异常。这使得自定义连接器能够在有时间限制的情况下确定结构化并发工作如何完成。

 

常见的连接器Joiner.allSuccessfulOrThrow()现在直接从 scope.join()返回 List,而不是 Subtask Handles 的流。在常见的用例中,这可以减少样板代码。

 

简洁明了起见,anySuccessfulResultOrThrow()已重命名为anySuccessfulOrThrow(),而行为保持不变(返回第一个成功的结果,取消其余操作)。

 

现在,StructuredTaskScope.open()的重载方法在接受配置修饰符时,预期参数类型已从 Function 改为 UnaryOperator。该变更旨在收紧 API 规范并防止意外的类型混用。

 

基本使用模式保持不变。首先打开一个结构化任务作用域,分叉相关子任务,通常是在虚拟线程上,并通过调用 join()等待其完成。作用域可以确保当块退出时,所有子任务要么已完成要么已被取消。

 

try (var scope = StructuredTaskScope.open()) {    var user = scope.fork(() -> fetchUser(userId));    var order = scope.fork(() -> fetchOrder(userId));    scope.join(); // 等待所有子任务    String name = user.get();    int count = order.get();}
复制代码

 

当配置了超时时,自定义连接器可能会处理超时并返回部分结果,而不是使 join()因异常而失败。

 

class PartialCollector<T> implements StructuredTaskScope.Joiner<T, List<T>> {    private final Queue<T> results = new ConcurrentLinkedQueue<>();    @Override    public boolean onComplete(StructuredTaskScope.Subtask<T> st) {        if (st.state() == StructuredTaskScope.Subtask.State.SUCCESS) {            results.add(st.get());        }        return false;    }    @Override    public void onTimeout() {        IO.println("Timed out, returning partial results");    }    @Override    public List<T> result() {        return List.copyOf(results);    }}try (var scope = StructuredTaskScope.open(    new PartialCollector<String>(),    cfg -> cfg.withTimeout(Duration.ofSeconds(1)))) {    scope.fork(() -> fetchA());    scope.fork(() -> fetchB());    List<String> partial = scope.join(); // 超时时的部分结果    IO.println("Partial results: " + partial);}
复制代码

 

在这个例子中,自定义连接器收集已完成的子任务,并在作用域超时时返回累积的结果,而不是抛出 TimeoutException 异常导致操作失败。

 

预览版 6 并没有大幅修改预览版 5 中引入的 API,而是致力于提高它们在常见使用模式中的易用性和灵活性。新增的超时钩子可以更为优雅地处理速度慢或不可预测的子任务,而不会削弱结构化保证。将结果作为列表而不是流返回可以简化结果处理过程,而统一的命名规范与更精简的配置 API 则进一步完善了整体设计。

 

对于已经在试用虚拟线程和结构化任务作用域的团队来说,这些改进解决了早期预览版中观察到的问题,而该功能仍然处于预览阶段。

 

结构化并发是 Loom 项目更广泛的并发路线图的关键组成部分。结合虚拟线程使用时,它能够以结构化的方式管理明确定义了生命周期的相关并发任务。预览版 6 延续了迭代预览流程,其变更基于针对前期预览版本的反馈意见。

 

开发人员可以通过在 JDK 26 早期访问构建中启用预览功能(--enable-preview)来尝试结构化并发,并通过 OpenJDK 邮件列表提供反馈。

 

原文链接:

https://www.infoq.com/news/2026/01/timeout-joiner-refinements/