Java的虚拟线程(协程)特性开启预览阶段,多线程开发的难度将大大降低

高并发、多线程一直是 Java编程中的难点,也是面试题中的要点。 Java开发者也一直在尝试使用多线程来解决应用服务器的并发问题。但是多线程并不容易,为此一个新的技术出现了,这就是虚拟线程。

传统多线程的痛点

但是编写多线程代码是非常不容易的,难以控制的执行顺序,共享变量的线程安全性,异常的可观察性等等都是多线程编程的难点。

如果每个请求在请求的持续时间内都在一个线程中处理,那么为了提高应用程序的吞吐量,线程的数量必须随着吞吐量的增长而增长。不幸的是线程是稀缺资源,创建一个线程的代价是昂贵的,即使引入了池化技术也无法降低新线程的创建成本,而且 JDK 当前的线程实现将应用程序的吞吐量限制在远低于硬件可以支持的水平。

为此很多开发人员转向了异步编程,例如 CompletableFuture或者现在正热的反应式框架。但是这些技术要么摆脱不了”回调地狱”,要么缺乏可观测性。

解决这些痛点、增强Java平台的和谐,实现每个请求使用独立线程(thread-per-request style)这种风格成为必要之举。能否实现一种”成本低廉”的虚拟线程来映射到系统线程以减少对系统线程的直接操作呢?思路应该是没问题的!于是Java社区发起了关于虚拟线程的JEP 425提案。

虚拟线程

虚拟线程( virtual threads)应该非常廉价而且可以无需担心系统硬件资源被大量创建,并且不应该被池化。应该为每个应用程序任务创建一个新的虚拟线程。因此,大多数虚拟线程将是短暂的并且具有浅层调用堆栈,只执行单个任务 HTTP 客户端调用或单个 JDBC 查询。与之对应的 平台线程( Platform Threads,也就是现在传统的JVM线程 )是重量级且昂贵的,因此通常必须被池化。它们往往寿命长,有很深的调用堆栈,并且在许多任务之间共享。

总而言之,虚拟线程保留了与 Java 平台的设计相协调的、可靠的独立请求线程(thread-per-request style),同时优化了硬件的利用。使用虚拟线程不需要学习新概念,甚至需要改掉现在操作多线程的习惯,使用更加容易上手的 API、兼容以前的多线程设计、并且丝毫不会影响代码的拓展性。

为了更好理解这一个设计,草案对这两种线程进行了比较。

现在每个 java.lang.Thread都是一个平台线程,平台线程在底层操作系统线程上运行 Java 代码,并在代码的整个生命周期内捕获操作系统线程。平台线程数受限于 OS 线程数。

平台线程并不会因为加入虚拟线程而退出历史舞台。

虚拟线程是由 JDK 而不是操作系统提供的线程的轻量级实现。它们是用户模式线程的一种形式,在其他多线程语言中已经成功(比如Golang中的协程和Erlang中的进程)。 虚拟线程采用 M:N 调度,其中大量 (M) 虚拟线程被调度为在较少数量 (N) 的 OS 线程上运行。 JDK 的虚拟线程调度程序是一种 ForkJoinPool工作窃取的机制,以 FIFO 模式运行。

我们可以很随意地创建10000个虚拟线程:

// 预览代码
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
    });
}

无需担心硬件资源是否扛得住,反过来如果你使用 Executors.newCachedThreadPool()创建10000个平台线程,在大多数操作系统上很容易因资源不足而崩溃。

但是这里依然要说明一点, 虚拟线程并是为了提升执行速度而设计。它并不比平台线程速度快,它们的存在是为了提供规模(更高的吞吐量),而不是速度(更低的延迟)。它们的数量可能比平台线程多得多,因此根据利特尔定律,它们可以实现更高吞吐量所需的更高并发性。

换句话说,虚拟线程可以显着提高应用程序吞吐量

  • 并发任务的数量很高(超过几千个),并且
  • 工作负载不受 CPU 限制,因为在这种情况下,拥有比处理器内核多得多的线程并不能提高吞吐量。

虚拟线程有助于提高传统服务器应用程序的吞吐量,正是因为此类应用程序包含大量并发任务,这些任务花费大量的时间等待。

编写清晰的代码并不是全部。对正在运行的程序状态的清晰表示对于故障排除、维护和优化也很重要,JDK 长期以来一直提供调试、分析和监视线程的机制。 在虚拟线程中也会增强代码的可观测性,让开发人员更好地调试代码。

新的线程API

为此增加了新的线程API设计,目前放出的部分如下:

  • Thread.Builder 线程构建器。
  • ThreadFactory 能批量构建相同特性的线程工厂。
  • Thread.ofVirtual() 创建一个虚拟线程。
  • Thread.ofPlatform() 创建一个平台线程。
  • Thread.startVirtualThread(Runnable) 一种创建然后启动虚拟线程的便捷方式。
  • Thread.isVirtual() 测试线程是否是虚拟线程。

还有很多就不一一演示了,有兴趣的自行去看 JEP425

JEP425还有很多的细节,基于我个人理解能力的不足只能解读这么多了。协程在Java社区已经呼唤了很久了,现在终于有了实质性的动作,这是一个令人振奋的好消息。不过这个功能涉及的东西还是很多的,包括平台线程的兼容性、对 ThreadLocal的一些影响、对 JUC的影响。可能需要多次预览才能最终落地。胖哥可能赶不上那个时候了,不过很多年轻的同学应该能够赶上。

关注公众号:Felordcn 获取更多资讯

Original: https://www.cnblogs.com/felordcn/p/16115793.html
Author: 码农小胖哥
Title: Java的虚拟线程(协程)特性开启预览阶段,多线程开发的难度将大大降低

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/538006/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Dart/Flutter 命名冲突,导致import重复,变异失败,Compiler message: is imported from both

    Log: 重复导入,导致变异失败。 两种解决方案: 1.使用hide,把其中一个improt 使用hide隐藏掉冲突名字 2.使用as,重命名 Original: https://…

    Java 2023年5月29日
    068
  • Lambda表达式

    1.常见单方法接口 Comparator Runnable Callable @FunctionalInterface 只定义了单方法的接口称之为 FunctionalInterf…

    Java 2023年6月13日
    091
  • 【软件构造】Java设计模式

    创建模式:关注对象的创建过程 简单工厂模式 实现流程: 将需要创建的各种不同的产品对象封装到不同的类中,成为具体产品类 将具体产品类的公共代码进行提取封装到抽象产品类中,具体产品类…

    Java 2023年6月5日
    071
  • 力扣刷题之路—–括号匹配问题

    括号匹配问题在力扣中有好几道,简单的括号匹配问题即一个左括号需要对应一个右括号,判断是否匹配或者需要加入几个左括号或右括号,此时的问题比较简单,当字符串中的字符只有一种类型的括号时…

    Java 2023年6月5日
    098
  • 20220808-单例设计模式

    1. 设计模式 2 单例设计模式 2.1 懒汉式 2.2 饿汉式 2.3 区别 1. 设计模式 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。 使用…

    Java 2023年6月15日
    073
  • 深入MySQL(二):MySQL的数据类型

    对于MySQL中的数据类型的选择,不同的数据类型看起来可能是相同的效果,但是其实很多时候天差地别。本章从MySQL中的 常用类型出发,结合 类型选择的常见错误,贯彻MySQL的常用…

    Java 2023年6月7日
    097
  • JAVA基础-接口和抽象类

    个人经验,抽象类工作中很少见。 不过鉴于一直对这些概念很模糊,所以学习记录一下。 一、什么是抽象类? 简单来说,不管是抽象类还是抽象方法都必须用 abstract 关键字进行 声明…

    Java 2023年6月5日
    077
  • Day17

    告诉大家该怎样学? 这是什么? 它怎么玩? 该如何在我们平时运用? 窗口 弹窗 面板 文本框 列表框 按钮 图片 监听事件 鼠标 键盘事件 破解工具 1、简介 GUI的核心技术:S…

    Java 2023年6月5日
    066
  • 分布式系统中数据存储方案实践

    数据膨胀的时候,必然放大细节。 一、背景简介 在项目研发的过程中,对于数据存储能力的依赖无处不在,项目初期,相比系统层面的组件选型与框架设计,由于数据体量不大,在存储管理方面通常容…

    Java 2023年6月15日
    072
  • shell 编程

    shell 编程 Shell 是一个命令行解释器,它为用户提供了一个向 Linux 内核发送请求以便运行程序的界面系统级程序,用户可以用 Shell 来启动、挂起、停止甚至是编写一…

    Java 2023年6月5日
    078
  • nginx 伪静态跳转

    301跳转要带着 permanent; 302 不带permanent rewrite ^.*$ https://www.hahaha.com/$1 permanent; Orig…

    Java 2023年5月30日
    066
  • 10分钟搞定让你困惑的 Jenkins 环境变量

    前言 Jenkins, DevOps 技术栈的核心之一,CI/CD 离不开编写 Pipeline 脚本,上手 Jenkins ,简单查一下文档,你就应该不会被 agent,stag…

    Java 2023年6月5日
    062
  • Spring5.0源码学习系列之Spring AOP简述

    前言介绍 附录:Spring源码学习专栏 在前面章节的学习中,我们对Spring框架的IOC实现源码有了一定的了解,接着本文继续学习Springframework一个核心的技术点A…

    Java 2023年5月30日
    0114
  • Maven使用总结

    Maven使用总结 一、Maven的主要作用 Maven 翻译为”专家”、”内行”,是 Apache 下的一个纯 Java 开发的开…

    Java 2023年6月8日
    086
  • 栈和队列

    写在前面 栈和队列,也属于线性表,因为它们也都用于存储逻辑关系为 “一对一” 的数据。使用栈结构存储数据,讲究 先进后出,即最先进栈的数据,最后出栈;使用队…

    Java 2023年6月5日
    056
  • JVM集合之开篇点题

    大家在平时的开发过程中是否遇到过 StackOverflowError、 OutOfMemoryError等类似的内存溢出错误呢?大家又是怎么解决这个问题的?再来,大家在面试过程中…

    Java 2023年6月5日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球