当前位置: 首页 > ds >正文

Java 并发新范式:用 Structured Concurrency 优雅收拾多线程烂摊子

1. 背景:为什么要有 Structured Concurrency

传统多线程(Thread + ExecutorService)的问题:

  • 线程生命周期难管理:启动的线程分散在不同地方,不容易跟踪,也不容易知道什么时候全部完成。
  • 错误传播复杂:一个线程抛异常时,调用方往往无感知,必须自己加监听或 future.get()。
  • 资源泄漏风险:启动的任务可能在调用方结束后还在跑,导致内存泄漏或数据错乱。
  • 可读性差:任务之间的逻辑关系不直观。

Structured Concurrency 借鉴了 Go、Kotlin(coroutineScope)等语言的理念,把并发任务看作一个整体结构来管理,保证:

  • 所有子任务的生命周期受父任务控制。
  • 任务异常能自动传播给父任务。
  • 任务必须在作用域结束前全部完成(或取消)。

2. 概念

Structured Concurrency 核心思想:

“并发任务的生命周期,应该和它们的作用域一致。”

Java 在 JEP 453(JDK 21 预览)和 JEP 462(JDK 22 第二次预览)中实现了该特性。

类位置:

java.util.concurrent.StructuredTaskScope

常用子类:

  • StructuredTaskScope.ShutdownOnFailure
    任一任务失败,就取消其它任务,父任务抛异常。
  • StructuredTaskScope.ShutdownOnSuccess
    任一任务成功,就取消其它任务,返回该结果。

3. 基本用法示例

假设我们需要并发调用两个远程接口,取最快返回的结果:

import java.util.concurrent.*;public class StructuredConcurrencyExample {public static void main(String[] args) throws InterruptedException, ExecutionException {try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {scope.fork(() -> fetchFromServerA()); // 子任务1scope.fork(() -> fetchFromServerB()); // 子任务2scope.join();   // 等待所有任务结束(成功或被取消)String result = scope.result(); // 获取第一个成功的结果System.out.println("结果: " + result);}}static String fetchFromServerA() throws InterruptedException {Thread.sleep(2000);return "A的数据";}static String fetchFromServerB() throws InterruptedException {Thread.sleep(1000);return "B的数据";}
}

输出:

结果: B的数据

特点:

  • try-with-resources 确保作用域结束时自动清理。
  • 不用手动管理 Future 或线程取消。
  • 异常会自动向外传播。

4. 错误传播与取消

如果用 ShutdownOnFailure,任一任务失败就会:

  • 取消其它任务
  • 把异常抛给父任务

示例:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {scope.fork(() -> {Thread.sleep(1000);return "OK";});scope.fork(() -> {throw new RuntimeException("任务失败");});scope.join();       // 会在这里感知异常scope.throwIfFailed(); // 把异常向上传递
}

5. 对比传统 ExecutorService

传统写法:

var executor = Executors.newFixedThreadPool(2);
Future<String> f1 = executor.submit(this::task1);
Future<String> f2 = executor.submit(this::task2);
String r1 = f1.get();
String r2 = f2.get();
executor.shutdown();

缺点:

  • 必须手动调用 get() 捕获异常
  • 线程池要手动关闭
  • 任务之间的取消要手动管理

Structured Concurrency:

  • 自动取消关联任务
  • 作用域结束自动清理
  • 错误自动传播

6. 适用场景

  • 聚合多来源数据(如调用多个 API 取最快或最全结果)
  • 任务容错(一个失败自动取消其它任务)
  • 父子任务生命周期绑定(避免后台线程失控)
  • 搜索、并发计算(取第一个满足条件的结果)

7. 当前状态

  • JDK 21:首次作为预览特性(JEP 453)
  • JDK 22:第二次预览(JEP 462),增加了更细粒度的任务结果 API
  • 未来有可能在 JDK 23/24 正式定稿

8. 总结

优点:

  • 生命周期与作用域绑定,防止线程泄漏
  • 自动任务取消和异常传播
  • 可读性高,逻辑结构清晰
  • Virtual Threads 搭配更强(几千个并发任务也能高效运行)

缺点:

  • 目前还在预览阶段,可能有 API 变化
  • 需要 Java 21+ 才能用
http://www.xdnf.cn/news/17945.html

相关文章:

  • Linux软件编程:进程和线程
  • 【软考中级网络工程师】知识点之入侵防御系统:筑牢网络安全防线
  • Linux中Samba服务配置与使用指南
  • 计算机毕设大数据选题推荐 基于spark+Hadoop+python的贵州茅台股票数据分析系统【源码+文档+调试】
  • 百川开源大模型Baichuan-M2的医疗能力登顶第一?
  • Flink CDC 实战:实时监听 MySQL Binlog 并同步到 Kafka
  • 《贵州棒球百科》体育赛事排名·棒球1号位
  • 面试题:如何用Flink实时计算QPS
  • 【120页PPT】人工智能与数字化转型的业财融合(附下载方式)
  • 计算机视觉第一课opencv(二)保姆级教
  • 解决SQL Server连接失败:Connection refused: connect
  • H.264、H.265 到 H.266:编码标准演进、RTSP支持与实时视频系统实战
  • 嵌入式学习(day26)frambuffer帧缓冲
  • Vue内置组件全解析:从入门到面试通关
  • 三种DuckDB电子表格插件的union all查询性能对比
  • 基于C语言基础对C++的进一步学习_C和C++编程范式、C与C++对比的一些补充知识、C++中的命名空间、文件分层
  • 流处理 or 批处理?大数据架构还需要流批一体吗?
  • 看懂 Linux 硬件信息查看与故障排查
  • LRU缓存
  • ABP vNext 的工业时间序列治理:InfluxDB vs TimescaleDB 落地对比
  • C++ list模拟实现
  • 推荐系统论文分享之多任务模型--PLE(二)
  • 内存可见性和伪共享问题
  • 【COMSOL】Comsol学习案例时的心得记录分享
  • nginx高性能web服务器实验
  • FPGA+护理:跨学科发展的探索(四)
  • 集成电路学习:什么是Image Processing图像处理
  • AI + 数字孪生:解锁物业 “立体透明” 新范式
  • 学习日志33 python
  • 第二十二天:指针与内存