Java 中的结构化并发模式

news/2025/11/24 15:14:06/文章来源:https://www.cnblogs.com/icodewalker/p/19264293

并发编程长期以来一直是 Java 的阿喀琉斯之踵。尽管 ExecutorServiceFuture 为我们提供了良好的服务,但它们允许不受限制的模式,其中子任务可能比其父任务存活更久、线程可能泄漏,而取消操作则变成了一场噩梦。结构化并发通过将运行在不同线程中的相关任务组视为一个单一的工作单元,改变了这一现状,它简化了错误处理和取消操作,同时提高了可靠性和可观测性。

非结构化并发的问题

考虑一个使用 ExecutorService 的典型模式:一个线程创建执行器,另一个线程提交工作,而执行任务的线程与前两者都没有关系。在一个线程提交工作之后,一个完全不同的线程可以等待结果——任何持有 Future 引用的代码都可以连接它,甚至可以是与获取该 Future 的线程不同的线程中的代码。

这种非结构化方法带来了实际问题。当父任务未能正确关闭子任务时,就会发生线程泄漏。由于没有协调的方式来通知多个子任务,取消操作会出现延迟。并且由于任务和子任务之间的关系在运行时未被跟踪,可观测性会受到影响。

// 非结构化:关系是隐式且脆弱的
ExecutorService executor = Executors.newCachedThreadPool();
Future<User> userFuture = executor.submit(() -> fetchUser(id));
Future<Orders> ordersFuture = executor.submit(() -> fetchOrders(id));// 如果 fetchUser 失败会发生什么?
// 谁负责关闭执行器?
// 如果我们忘记清理,线程会泄漏吗?

引入 StructuredTaskScope

结构化并发 API 的主要类是 java.util.concurrent 包中的 StructuredTaskScope,它使您能够将一个并发子任务组作为一个单元进行协调。使用 StructuredTaskScope,您可以在各自的线程中分叉每个子任务,然后将它们作为一个单元进行汇合,确保在主任务继续之前子任务完成。

该 API 遵循一个清晰的模式:

  1. 使用 try-with-resources 创建一个 StructuredTaskScope
  2. 将子任务定义为 Callable 实例
  3. 在各自的线程中分叉每个子任务
  4. 汇合以等待完成
  5. 处理子任务的结果

以下是一个获取天气数据的真实示例:

WeatherReport getWeatherReport(String location)throws ExecutionException, InterruptedException {try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {Supplier<Temperature> temperature =scope.fork(() -> getTemperature(location));Supplier<Humidity> humidity =scope.fork(() -> getHumidity(location));Supplier<WindSpeed> windSpeed =scope.fork(() -> getWindSpeed(location));scope.join()           // 汇合所有子任务.throwIfFailed(); // 如果有任何失败,传播错误// 全部成功,组合结果return new WeatherReport(location,temperature.get(),humidity.get(),windSpeed.get());}
}

try-with-resources 代码块至关重要——它确保作用域被正确关闭,取消任何未完成的子任务并防止线程泄漏。

使用关闭策略实现短路

短路模式通过使主任务能够中断和取消那些不再需要其结果子任务,来促使子任务快速完成。两个内置策略处理了常见场景:

ShutdownOnFailure:"调用所有"模式

当您需要所有子任务都成功时,ShutdownOnFailure 会在一个任务失败后立即取消剩余的任务:

Response handleRequest(String userId) throws Exception {try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {Subtask<User> user = scope.fork(() -> fetchUser(userId));Subtask<Profile> profile = scope.fork(() -> fetchProfile(userId));Subtask<Settings> settings = scope.fork(() -> fetchSettings(userId));scope.join().throwIfFailed();// 如果有任何失败,我们永远不会到达这里return new Response(user.get(), profile.get(), settings.get());}
}

如果 fetchUser() 抛出异常,作用域会立即取消配置文件和设置的获取。没有浪费的工作,没有线程泄漏。

ShutdownOnSuccess:"调用任一"模式

有时您只需要第一个成功的结果——例如查询多个数据中心或尝试备用服务:

String fetchFromMultipleSources(String key) throws Exception {try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {scope.fork(() -> fetchFromPrimaryDB(key));scope.fork(() -> fetchFromCache(key));scope.fork(() -> fetchFromBackup(key));scope.join();// 返回第一个成功的结果return scope.result();}
}

任何子任务成功的瞬间,作用域就会取消其他任务。这种模式非常适合对延迟敏感的操作,即您需要竞速多个来源。

自定义关闭策略

在实践中,大多数 StructuredTaskScope 的使用不会直接使用 StructuredTaskScope 类,而是使用实现了关闭策略的两个子类之一,或者编写自定义子类来实现自定义关闭策略。

以下是一个收集所有成功结果并忽略失败的自定义策略:

class AllSuccessesScope<T> extends StructuredTaskScope<T> {private final List<T> results =Collections.synchronizedList(new ArrayList<>());@Overrideprotected void handleComplete(Subtask<? extends T> subtask) {if (subtask.state() == Subtask.State.SUCCESS) {results.add(subtask.get());}}public List<T> getResults() {return List.copyOf(results);}
}// 用法
List<Data> collectAll() throws InterruptedException {try (var scope = new AllSuccessesScope<Data>()) {for (String source : dataSources) {scope.fork(() -> fetchData(source));}scope.join();return scope.getResults();}
}

虚拟线程:完美搭档

虚拟线程提供了大量的线程——结构化并发可以正确且健壮地协调它们,并使可观测性工具能够按开发人员理解的方式显示线程。这种组合非常强大,因为虚拟线程使得创建数百万个线程的成本很低,而结构化并发则确保您能安全地管理它们。

// 现在启动 10,000 个并发任务是可行的
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {for (int i = 0; i < 10_000; i++) {final int taskId = i;scope.fork(() -> processTask(taskId));}scope.join().throwIfFailed();
}

使用平台线程,这将是灾难性的。但使用虚拟线程和结构化并发,这变得简单而安全。

模块系统考量

在使用结构化并发构建模块化应用程序时,理解 Java 的模块系统变得很重要。对于模块,反射失去了其"超能力",并且受限于与编译代码完全相同的可访问性规则——它只能访问导出包中公共类的公共成员。

默认情况下,只有 module-info.java 中显式导出的包是可见的。如果您使用的是依赖反射的框架(如 Spring 或 Hibernate),您将需要额外的声明:

module com.example.app {// 用于编译时访问的常规导出exports com.example.api;// 为运行时反射访问开放opens com.example.entities to org.hibernate.orm.core;requires java.base;requires org.hibernate.orm.core;
}

在编译时,开放的包完全被封装,就像该指令不存在一样,但在运行时,包的类型可用于反射,自由地与所有类型和成员(无论公开与否)交互。

为了在所有包上获得完整的反射访问权限,您可以声明一个开放模块:

open module com.example.app {exports com.example.api;requires java.base;
}

开放模块会开放其包含的所有包,就像每个包都单独在 opens 指令中使用一样,这很方便但降低了封装性。

可观测性和调试

结构化并发显著提高了可观测性。线程转储现在显示了清晰的父子关系:

jcmd <pid> Thread.dump_to_file -format=json output.json

JSON 输出揭示了 StructuredTaskScope 及其在数组中的分叉子任务,使得理解正在运行的内容及其原因变得容易。这与关系隐式的扁平线程转储相比,是一种变革。

当前状态与演进

结构化并发由 JEP 428 提出,并在 JDK 19 中作为孵化 API 交付,在 JDK 20 中重新孵化,通过 JEP 453 在 JDK 21 中首次预览,并在 JDK 22 和 23 中重新预览。截至 JDK 25,该 API 已经演进,使用静态工厂方法替代了公共构造函数。

要在当前 JDK 版本中使用结构化并发,需启用预览特性:

# 编译
javac --release 21 --enable-preview MyApp.java# 运行
java --enable-preview MyApp

基于真实世界的反馈,该 API 正在稳定下来。结构化并发已被证明是一种安全、富有表现力且易于理解的并发方法,Python 库率先开创了这一领域,随后是 Kotlin 等语言。

最佳实践

  • 始终使用 Try-With-Resources:必须关闭作用域以防止线程泄漏。切勿手动管理 StructuredTaskScope 的生命周期。
  • 选择正确的策略:当所有结果都重要时使用 ShutdownOnFailure,在竞速场景中使用 ShutdownOnSuccess,或者为特定需求实现自定义策略。
  • 与虚拟线程结合使用:结构化并发与虚拟线程结合时效果最佳,能够通过简单的代码实现大规模并发。
  • 避免共享可变状态:虽然结构化并发处理协调,但您仍然需要对共享数据的线程安全负责。
  • 考虑作用域值:为了在任务层次结构中传递上下文,作用域值(JEP 481)提供了比 ThreadLocal 更好的替代方案。

真实示例:聚合用户数据

让我们构建一个从多个来源聚合数据的完整示例:

public class UserAggregator {record UserData(User user, List<Order> orders,Stats stats, Recommendations recs) {}public UserData aggregate(String userId)throws ExecutionException, InterruptedException {try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {Supplier<User> user =scope.fork(() -> userService.fetch(userId));Supplier<List<Order>> orders =scope.fork(() -> orderService.fetch(userId));Supplier<Stats> stats =scope.fork(() -> statsService.compute(userId));Supplier<Recommendations> recs =scope.fork(() -> mlService.recommend(userId));scope.join().throwIfFailed();return new UserData(user.get(),orders.get(),stats.get(),recs.get());}}
}

这种模式简洁、安全且高效。如果任何服务失败,所有其他服务会立即被取消。作用域确保适当的清理。并且借助虚拟线程,这可以扩展到数千个并发请求。

开发者观点

Java 架构师决定不从 fork 方法返回 Future 实例,以避免与非结构化计算混淆,并与旧的并发模型进行清晰切割。这一设计决策强调了结构化并发是一种新的范式,而不仅仅是渐进式改进。

Rock the JVM 教程指出,结构化并发最终为 Java 带来了其他 JVM 语言通过 Kotlin 协程和 Scala Cats Effects Fibers 等库所提供的功能,但拥有官方的平台支持。

展望未来

结构化并发代表了我们对并发编程思考方式的根本转变。我们不是管理单个线程和 Future,而是按层次结构组织并发工作——就像我们用方法和循环组织顺序代码一样。

好处是显而易见的:没有线程泄漏、正确的错误传播、协调的取消以及增强的可观测性。结合虚拟线程,Java 现在提供了一个既强大又易于使用的并发模型。

随着该 API 走向最终化,预计将在框架和库中得到更广泛的采用。Spring、Hibernate 及其他生态系统项目已经在考虑如何利用结构化并发来编写更清晰、更可靠的并发代码。


【注】本文译自:Structured Concurrency Patterns in Java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/974898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ddddocr: 得到滑块的目标位置

一,代码: import base64 from ddddocr import DdddOcrimport numpy as np from PIL import Image import io from PIL import Image, ImageFilter from io import BytesIOocr = DdddOcr(det=False, ocr=False)with o…

小满的五年心得体会(程序人生) - 教程

小满的五年心得体会(程序人生) - 教程2025-11-24 15:09 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !i…

ChatGPT响应背后的47步技术之旅

本文深入解析用户向ChatGPT发送"Hello"后0.8秒内发生的完整技术流程,包括12个系统处理、1750亿参数神经网络运作、47个计算步骤等关键技术细节,揭示大语言模型背后的复杂基础设施。你输入"Hello"…

2025 最新山东青岛进口公司排行榜,全链路进口清关优质企业最新推荐 涵盖多品类专业服务权威榜单机械进口代理/海关进口代理/进口通关/进口报关行/代理进口公司推荐

引言 在全球贸易一体化加速推进的背景下,进口代理与清关服务的专业性、高效性成为企业跨境贸易成功的关键。本次排行榜由国际物流与贸易协会联合权威测评机构打造,基于近一年全球 1200 余家进口服务商的实测数据,通…

我在赛时写下了如下代码,你也来试试吧

#include<iostream> #include<vector> #include<algorithm> #include<climits> using namespace std; #define getc getchar_unlocked #define putc putchar_unlocked #define ed cout<<…

cmake使用教程 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025 年 11 月实验室净化工程厂家权威推荐榜:洁净室/手术室/无尘车间建设装修全方案解析,专业技术与高效服务深度评测

2025 年 11 月实验室净化工程厂家权威推荐榜:洁净室/手术室/无尘车间建设装修全方案解析,专业技术与高效服务深度评测 行业背景与发展趋势 随着生物医药、精密制造、医疗卫生等行业的快速发展,对洁净环境的需求日益…

催化剂过滤回收哪家好?相关领域企业选择参考

催化剂过滤回收是化工、医药、新材料等行业生产过程中的重要环节,其效率和质量直接影响资源利用率与生产安全性。通过有效的过滤回收工艺,不仅能实现催化剂的循环利用,还能减少废弃物排放,推动行业绿色发展。 一、…

NOIP 模拟赛 8 总结

分数:\(100 + 0 + 0 + 0 = 100\) 永康喵喵坠机了! 别样的数数大战。 T1 这道题本身非常糖,但是我更糖。 考虑 \(n \le 5000\),我们可以一边遍历 \(a\),设当前遍历到了 \(a_i\),记录 \(a_1 + a_{i-1}, a_2 + a_{i…

智能交通新引擎:国标GB28181算法算力平台EasyGBS打造城市交通路口智能感知中枢

智能交通新引擎:国标GB28181算法算力平台EasyGBS打造城市交通路口智能感知中枢在城市交通治理中,路口作为城市道路的“神经末梢”,其运行状态直接决定了整体交通效率与安全。传统交通路口监控系统面临着设备品牌杂乱…

锁的失效和分布式锁的实现

1. 引言 在多线程编程和分布式系统设计中,锁是保证数据一致性和线程安全的重要机制。本文将深入探讨从单体式应用到分布式系统中锁的实现与演进。 2. 单体式项目中的锁 2.1 synchronized关键字 在单体式项目中,最简单…

2025 年 11 月镀膜材料厂家权威推荐榜:真空镀膜材料、光学镀膜材料、纳米镀膜材料,创新工艺与高性能解决方案深度解析

2025 年 11 月镀膜材料厂家权威推荐榜:真空镀膜材料、光学镀膜材料、纳米镀膜材料,创新工艺与高性能解决方案深度解析 随着新能源、半导体、显示技术等高科技产业的快速发展,镀膜材料作为关键功能材料,在提升产品性…

2025 年 11 月臭氧检测仪厂家权威推荐榜:高精度检测与稳定性能,助力环境监测与工业安全

2025 年 11 月臭氧检测仪厂家权威推荐榜:高精度检测与稳定性能,助力环境监测与工业安全 臭氧检测仪作为环境监测与工业安全领域的关键设备,其精度与稳定性直接关系到空气质量评估、生产过程控制及人员健康保障。随着…

想要抚州PC耐力板加工?查行情享优惠高达30%

想要抚州PC耐力板加工?查行情享优惠高达30%在抚州地区,随着城市更新与基础设施建设的持续推进,对高性能建材的需求显著上升。其中,PC耐力板加工产品因其优异的抗冲击性、轻质高透光及良好的耐候性能,被广泛应用于…

晶圆清洗过滤哪家靠谱?行业内的实力之选

在半导体制造过程中,晶圆清洗过滤环节对产品良率和性能有着直接影响。高效的过滤技术能够有效去除微小颗粒和杂质,保障后续工艺的稳定性。随着行业对精度要求的不断提升,选择合适的过滤解决方案成为关键。 一、推荐…

2025年远传水表实力厂家权威推荐榜单:水表/插卡水表/热量表源头厂家精选

据QYResearch调研统计,2031年全球干式无线远传智能水表市场销售额预计将达亿元规模。在这个快速增长的市场中,选择一家技术可靠、生产实力雄厚的源头厂家变得至关重要。 远传水表作为智慧城市水资源管理的核心设备,…

2025年五香豆干优质厂家权威推荐榜单:豆干批发/泡椒豆干/花椒豆干源头厂家精选

五香豆干作为中国传统豆制品的重要组成部分,近年来在休闲食品市场和餐饮渠道保持着稳定的需求增长。随着消费者对健康食品和植物蛋白的关注度提升,豆制品行业年增长率维持在7%左右,其中五香豆干细分市场占比约15%。…

计算机系统大作业:软件人生-Hello’s P2P

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025 年 11 月断桥铝门窗实力厂家推荐榜:节能静音系统窗/阳台窗/定制门窗,匠心工艺与高性价比之选

2025 年 11 月断桥铝门窗实力厂家推荐榜:节能静音系统窗/阳台窗/定制门窗,匠心工艺与高性价比之选 随着建筑节能标准的不断提升和消费者对居住舒适度要求的日益增长,断桥铝门窗行业迎来了新一轮技术革新与市场洗牌。…

105_尚硅谷_continue执行流程分析

105_尚硅谷_continue执行流程分析1.continue执行流程案例1 2.continue执行流程案例2 3.continue执行流程案例3