bigywater.tech
Java · 186

Structured Concurrency:并发子任务如何统一取消和收集异常

结构化并发要解决「一个任务拆出的子任务各自泄漏、失败不会取消兄弟」的问题。这篇用 JDK 27 的 StructuredTaskScope(预览 API,用 --enable-preview 运行)验证:成功汇总、失败传播与取消、取第一个成功、超时(ScopedValue 与虚拟线程见前面的篇目)。

121 ms
两个 100 ms 子任务并行完成的总耗时
72 ms
一个子任务失败后 join 返回的时间(另一个子任务要 3000 ms)
Owner did not join
fork 之后没有 join 时抛出的 IllegalStateException

并行与汇总

实测 · 本机执行输出
  fork 之后、join 之前 u.state() = UNAVAILABLE
  join 之前调用 u.get() → IllegalStateException: null
  join 返回的结果列表:[user, orders],总耗时 121 ms(两个 100 ms 的子任务并行);u.state() = SUCCESS,u.get() = user

失败取消

实测 · 本机执行输出
  join 抛 ExecutionException,cause = java.lang.IllegalStateException: 子任务失败
  join 在 72 ms 后返回(慢任务要 3000 ms);fail.state() = FAILED,slow.state() = UNAVAILABLE,scope.isCancelled() = true
  慢任务是否被中断(收到 InterruptedException):true

取第一个成功

实测 · 本机执行输出
  anySuccessfulOrThrow 的结果 = 快的,耗时 71 ms(最快的失败了,取第一个成功的)
  还在跑的慢任务被中断了吗:true

超时与误用

实测 · 本机执行输出
  配置了 withTimeout(200 ms) 的 scope,子任务要 2000 ms:join 抛 ExecutionException,耗时 210 ms
  fork 了子任务却没有 join 就离开 try-with-resources → IllegalStateException: Owner did not join after forking
  子任务运行在 虚拟线程 里(每个 fork 默认一个虚拟线程)

分步演示

StructuredTaskScope
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 27,StructuredTaskScope 是预览 API(javac --enable-preview --release 27,运行时 --enable-preview);本篇只验证了 JDK 27 上的 API 形式(StructuredTaskScope.open(Joiner)),JDK 21 上的 API 本篇没有运行验证
    • 没有验证:JDK 21 上的 API、与 ScopedValue 的值继承(子任务继承绑定)、自定义 Joiner、嵌套 scope 的取消传播、线程转储里的结构化展示;预览 API 的行为在正式化时可能改变
    京ICP备2026067502号-1京公网安备11010502063487号