写点什么

JEP 428:结构化并发,简化 Java 多线程编程

作者:A N M Bazlur Rahman

  • 2022-06-17
  • 本文字数:2284 字

    阅读完需:约 7 分钟

JEP 428:结构化并发,简化Java多线程编程

JEP 428,即结构化并发(孵化器阶段),已经从 Proposed 状态进入到 Target 状态。在 Project Loom 的框架下,这个 JEP 提议引入一个库,将在不同线程中运行的多个任务视为原子操作,以此来简化多线程编程。它可以简化错误处理和取消操作,提高可靠性,并增强可观察性。这个 API 仍然在孵化当中。


开发人员可以使用 StructuredTaskScope 类来组织他们的并发代码,这个类将把一组子任务视为一个单元。子任务通过单独的线程创建,然后连接成一个单元,也可以作为一个单元进行取消。子任务的异常或执行结果将由父任务进行聚合和处理。让我们来看一个例子:


Response handle() throws ExecutionException, InterruptedException {   try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {       Future<String> user = scope.fork(() -> findUser());       Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // 连接 scope.throwIfFailed(); // 抛出错误
// 聚合结果 return new Response(user.resultNow(), order.resultNow()); }}
复制代码


上面的 handle()方法表示服务器应用程序的一个任务。它创建了两个子任务来处理传入的请求。与 ExecutorService.submit()一样,StructuredTaskScope.fork()接受 Callable 作为参数,并返回 Future。与 ExecutorService 不同的是,返回的 Future 不是通过 Future.get()来连接的。这个 API 运行在 JEP 425 之上——虚拟线程(预览阶段),发布目标也为 JDK 19。


上面的例子使用了 StructuredTaskScope API,如果要在 JDK 19 上运行它们,必须添加 jdk.incubator.concurrent 模块,同时要启用预览功能来使用虚拟线程。


使用下面的命令编译上述代码:


javac --release 19 --enable-preview --add-modules jdk.incubator.concurrent Main.java
复制代码


运行程序也需要提供相同的标志:


java --enable-preview --add-modules jdk.incubator.concurrent Main
复制代码


不过,我们也可以使用源代码启动器直接运行它,命令应该是这样的:


java --source 19 --enable-preview --add-modules jdk.incubator.concurrent Main.java
复制代码


jshell 也是可用的,但也需要启用预览功能:


jshell --enable-preview --add-modules jdk.incubator.concurrent
复制代码


结构化并发带来了很多好处。它为调用者方法及其子任务创建了一种父子关系。例如,在上面的例子中,handle()任务是父,它的子任务 findUser()和 fetchOrder()是子。结果,整个代码块变成了原子代码。它通过线程转储中的任务层次结构来提供可观察性。它还可以在错误处理中实现短路,如果其中一个子任务失败,其他未完成的任务将被取消。如果父任务的线程在 join()调用之前或期间被中断,两个分支将在作用域退出时自动取消。这让并发代码的结构变得更加清晰,开发人员现在可以推理和跟踪代码,就好像它们是在单线程环境中运行。


早期的程序流程普遍使用 GOTO 语句来控制,代码十分混乱,这种意大利面条式的代码难以阅读和调试。随着编程范式的成熟,编程社区认识到 GOTO 语句是有害的。1969 年,以《计算机编程的艺术》一书而闻名的计算机科学家 Donald Knuth 表示,没有 GOTO 也可以高效地编写程序。后来,结构化编程的出现解决了所有这些缺点。看一下下面的例子:


Response handle() throws IOException {   String theUser = findUser();   int theOrder = fetchOrder();   return new Response(theUser, theOrder);}
复制代码


上面的代码是结构化代码的一个例子。在单线程环境中,handle()方法被调用时将按顺序执行。fetchOrder()方法不会在 findUser()方法之前启动。如果 findUser()方法失败,下面的方法根本不会启动,handle()方法将隐式失败,这反过来确保了原子操作成功或不成功。它提供了 handle()方法及其子方法之间的父子关系,遵循错误传播的规则,并在运行时提供调用堆栈信息。


然而,这种方法和推理并不适用于我们当前的线程编程模型。例如,如果我们想用 ExecutorService 改写上述的代码,就像这样:


Response handle() throws ExecutionException, InterruptedException {   Future<String>  user  = executorService.submit(() -> findUser());   Future<Integer> order = executorService.submit(() -> fetchOrder());   String theUser  = user.get();   // 连接findUser   int theOrder = order.get();  // 连接fetchOrder   return new Response(theUser, theOrder);}
复制代码


ExecutorService 中的子任务独立运行,可能成功或失败。即使父任务被中断,中断也不会被传播到子任务,因此会造成泄漏。它没有了父关系。由于父任务和子任务将出现在线程转储不相关的线程调用堆栈上,因此调试也变得困难。尽管代码看起来具有逻辑结构,但这种结构只停留在开发人员的头脑中,而不是在执行过程中。所以,它们是非结构化的并发代码。


通过观察非结构化并发代码存在的这些问题,Martin Sústrik 在他的博文中创造了“结构化并发”这个术语,然后 Nathaniel J. Smith 在他关于结构化并发的文章中推广了这个术语。关于结构化并发,Oracle 技术咨询成员、Loom 项目负责人 Ron Pressler 在 InfoQ 的一个播客中说道:


结构化的意思是,如果你生成了什么东西,你必须等待并连接它。这里的“结构”与它在结构化编程中的含义相似。代码的块结构反映了程序的运行时行为。因此,就像结构化编程提供了顺序控制流保证,结构化并发也为并发提供了同样的保证。有兴趣深入了解结构化并发及其背景故事的开发者可以收听 InfoQ 的博客,或者观看 Ron Pressler 在YouTube上的分享以及Inside Java的文章。


原文链接

JEP 428: Structured Concurrency to Simplify Java Multithreaded Programming

2022-06-17 08:084888

评论

发布
暂无评论
发现更多内容

英特尔老矣,尚能“转”否?

脑极体

中国银行前行长李礼辉:区块链技术优势与产业前景

CECBC

区块链 数字货币

JVM系列-第一节:JVM简介、运行时数据区、内存分代模型

诸葛小猿

Java JVM JVM简介 运行时数据区 内存分代模型

甲方日常 39

句子

工作 随笔杂谈 日常

京东智联云4篇论文入选国际语音顶级大会Interspeech 2020

京东科技开发者

人工智能 大数据 语音识别

视频客服的应用和优点

anyRTC开发者

音视频 WebRTC 直播 RTC

谈谈贪官污吏

空山

今天你的idea崩了吗?分享6种“白嫖”正版idea的途径,真香定律

小Q

学习 架构 面试 开发 IntelliJ IDEA

为你总结了N个真实线上故障,从容应对面试官!

AI乔治

Java 架构 GC

区块链助力跨省、零材料办理 成都武侯打造“中国政务服务第一链”

CECBC

区块链

同学信誓旦旦地说,我司的系统从来不做性能调优!

沉默王二

Java 性能调优

30 岁的码农人生 ——人生至暗时,你依然能窥见光明

苹果看辽宁体育

程序员 程序人生 感悟

可视化数据科学中的概率分布以帮你更好地理解各种分布

计算机与AI

学习 数据科学

它可能是分布式系统中最重要的枢纽

架构师修行之路

分布式 微服务 注册

vim快捷键收藏版

良知犹存

vim

32个问题,学习Java虚拟机的运行时数据区

AI乔治

Java 架构 JVM JVM虚拟机原理

Dubbo 微服务调用时序图

Acker飏

什么是动态代理

Rayjun

Java 动态代理

算法学习1-- 数组常见问题

菜鸟小sailor 🐕

算法和数据结构

杰哥获奖了!

JackTian

Linux 程序人生 运维工程师 运维人生

分布式系统使用网关到底是好还是坏?

架构师修行之路

分布式 微服务 网关

系统从初期到支撑亿级流量,都经历了哪些架构的变迁?

冰河

分布式 微服务 系统架构 SOA 垂直架构

一次艰难的内存泄露排查,BeanUtils的锅

AI乔治

Java 架构

"全能"人才的概念真的对吗

supernova

创业 读书笔记 随笔杂谈

AES加密模式

Mars

AES

记一次Jvm参数调优实战

AI乔治

Java 架构 JVM 编程思维 jvm调优

谷歌朝南,华为朝北

脑极体

如何在微信公众号图文中插入外链

Ceelog

微信公众号

LeetCode题解:47. 全排列 II,回溯,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

新基建下区块链基础设施建设

CECBC

区块链

自我管理系列-为啥要做生产问题分析报告呀

罗小龙

复盘 IT, 经验总结 职场搞笑 生产事故

JEP 428:结构化并发,简化Java多线程编程_语言 & 开发_InfoQ精选文章