前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >聊聊 Java 21 中的结构化并发(预览版)

聊聊 Java 21 中的结构化并发(预览版)

原创
作者头像
Lorin 洛林
发布2023-11-08 18:14:29
发布2023-11-08 18:14:29
5140
举报
文章被收录于专栏:Java 技术小屋Java 技术小屋
  • hello,大家好,我是 Lorin,今天和大家一起聊聊 Java 21 中另一个有意思的预览特性 - 结构化并发。

结构化编程

  • 在开始聊结构化并发之前,我们先简单聊聊一下结构化编程:

Goto Statement Considered Harmful

  • 在计算机发展的早期,程序员使用汇编语言进行编程,在之后的一段时期,诞生了比汇编略微高级的编程语言,如 FORTRAN、FLOW-MATIC 等。这些语言虽然在一定程度上提高了可读性,但是仍然存在很大的局限性。如下所示就是一段 FLOW-MATIC 代码:
  • 由于当时块语法还没有发明,因此 FLOW-MATIC 不支持 if 块、循环块、函数调用、块修饰符等现代语言必备的基础特性。整段代码就是一系列按顺序排列并打平的命令。关于控制流,程序支持两种方式,分别是:顺序执行、跳转执行,即 GOTO 语句。
  • 顺序执行的逻辑非常简单,它总是能够找到执行入口与出口。与之相反,跳转执行则充满了不确定性。如果程序中存在 GOTO 语句,那么它可以在 任何时候跳转至任何指令位置。一旦程序大量使用了 GOTO 语句,那么最终将变成 面条式代码(Spaghetti code)。如下图所示:

结构化编程

  • 在发表 《Goto Statement Considered Harmful》 之后,Dijkstra 又发表了 《Notes on Structured Programming》 表达了其理想的编程范式,提出了 结构化编程 的概念。
  • 结构化编程在现在看来是理所当然的,但是在当时并不是。结构化编程的核心是 基于块语句,实现代码逻辑的抽象与封装,从而保证控制流具有单一入口和单一出口。现代编程语言中的条件语句、循环语句、函数定义与调用都是结构化编程的体现。
  • 相比 GOTO 语句,基于块的控制流有一个显著的特征:控制流从程序入口进入,中途可能会经历条件、循环、函数调用等控制流转换,但是最终控制流都会从程序出口退出。这种编程范式使得代码结构变得更加结构化,思维模型变得更加简单,也为编译器在低层级提供了优化的可能。
  • 因此,完全禁用 GOTO 语句已经成为了大部分现代编程语言的选择。虽然,少部分编程语言仍然支持 GOTO,但是它们大都支持高德纳(Donald Ervin Knuth)所提出的前进分支和后退分支不得交叉的理论。类似 break、continue 等控制流命令,依然遵循结构化的基本原则:控制流拥有单一的入口与出口。

非结构化并发

  • 在开始了解结构化并发前,我们先回顾一下 Java 中非结构化并发的写法。
代码语言:java
复制
        ExecutorService executorService= Executors.newFixedThreadPool(3);
        Future<String> user = executorService.submit(() -> getUser());
        Future<Integer> order = executorService.submit(() -> getOrder());
        String theUser = user.get();   // 加入 getUser
        int theOrder = order.get();    // 加入 getOrder

非结构化并发存在的一些问题

线程泄漏

  • 当 getUser 或者 getOrder 抛出异常时,另外一个任务并不会停止执行,一方面会导致线程资源的浪费,另一方面可能干扰其它任务。
  • 又或者其中一个线程已经执行失败,继续执行的线程执行时间很长,这时候需要阻塞等待线程的完成,同样造成资源的浪费。

代码本身不会体现任务间的关系

  • 上面的各种情况其实都是在开发人员的脑海中,程序逻辑本身并不会体现出来,这样不仅会产生更多的错误空间,而且会使错误排查更加困难。

排查错误困难

  • 多线程编程中一个比较大的难点就是对错误的追踪,任务运行在不同的线程上,当然我们现在有跨线程追踪的方案,但是远远没有我们使用非并发编程时的简单和方便。

结构化并发

  • 在单线程编程模型中,编程语言 通过代码块避免控制流随意跳转,从而实现程序的结构化。但在多线程编程(并发编程)模型中,线程之间控制和归属关系仍然存在很多问题,其面临的问题与 GOTO 的问题非常相似,这也是结构化并发所要解决的问题。
  • 什么是结构化并发呢?结构化并发的核心是 在并发模型下,也要保证控制流的单一入口和单一出口。程序可以产生多个控制流来实现并发,但是所有并发控制流在出口时都应该处于完成或取消状态,控制流最终在出口处完成合并。
  • 下面是非结构化并发(图一)和结构化并发(图二)的运行示例图:

Java 结构化并发示例

代码语言:java
复制
public class Test {
    public static void main(String[] args) {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            Future<String> userFuture = scope.fork(() -> getUser());
            Future<Integer> orderFuture = scope.fork(() -> getOrder());
            scope.join()            // Join both subtasks
                    .throwIfFailed();  // ... and propagate errors
            System.out.println("User: " + userFuture.get());
            System.out.println("Order: " + orderFuture.get());
        } catch (ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private static int getOrder() throws Exception {
        // throw new Exception("test");
        return 1;
    }

    private static String getUser() {
        return "user";
    }

}

结构化并发带来的好处

  • 下面我们看看结构化并发如何解决非结构化并发中可能存在的一些问题:

短路处理

  • 如果一个getOrder()或getUser()一个子任务失败,则另一个尚未完成的任务将被取消。(这是由实施的关闭策略管理的ShutdownOnFailure;其他策略也是可能的,同时支持自定义策略)。避免了线程资源浪费以及可能的无意义阻塞。

取消传播

  • 如果线程在调用期间被中断join(),则当线程退出作用域时,两个子任务都会自动取消。避免了线程资源浪费。

清晰性

  • 上面的代码有一个清晰的结构:设置子任务,等待它们完成或被取消,然后决定是成功(并处理已经完成的子任务的结果)还是失败(没有什么需要清理的)。

可观察性

  • 线程转储 - 线程堆栈信息可以清楚的显示任务层次结构:
代码语言:java
复制
Exception in thread "main" java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.Exception: test
	at Test.main(Test.java:21)
Caused by: java.util.concurrent.ExecutionException: java.lang.Exception: test
	at jdk.incubator.concurrent/jdk.incubator.concurrent.StructuredTaskScope$ShutdownOnFailure.throwIfFailed(StructuredTaskScope.java:1188)
	at Test.main(Test.java:17)
Caused by: java.lang.Exception: test
	at Test.getOrder(Test.java:26)
	at Test.lambda$main$1(Test.java:15)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.lang.VirtualThread.run(VirtualThread.java:305)
	at java.base/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:177)
	at java.base/jdk.internal.vm.Continuation.enter0(Continuation.java:327)
	at java.base/jdk.internal.vm.Continuation.enter(Continuation.java:320)

总结

目前结构化并发的目标

  • 推广一种并发编程风格,可以消除因取消和关闭而产生的常见风险,例如线程泄漏和取消延迟。
  • 提高并发代码的可观察性。

以下不是目前非结构化并发的目标

  • 不会替换现有的任务并发结构。(java.util.concurrent package, such as ExecutorService and Future)
  • 为Java平台定义明确的结构化并发API并不是我们的目标。其他结构化并发结构可以由第三方库或在未来的JDK版本中定义。

参考

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结构化编程
    • Goto Statement Considered Harmful
    • 结构化编程
  • 非结构化并发
    • 非结构化并发存在的一些问题
      • 线程泄漏
      • 代码本身不会体现任务间的关系
      • 排查错误困难
  • 结构化并发
    • Java 结构化并发示例
    • 结构化并发带来的好处
      • 短路处理
      • 取消传播
      • 清晰性
      • 可观察性
  • 总结
    • 目前结构化并发的目标
    • 以下不是目前非结构化并发的目标
  • 参考
  • 个人简介
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档