首页 > 编程语言 >Java 21新特性-虚拟线程 审核中

Java 21新特性-虚拟线程 审核中

时间:2023-10-10 09:56:33浏览次数:43  
标签:Java 21 Thread 平台 虚拟 线程 轻量级

本文翻译自国外论坛 medium,原文地址:https://medium.com/@benweidig/looking-at-java-21-virtual-threads-0ddda4ac1be1

Java 21 版本更新中最重要的功能之一就是虚拟线程 (JEP 444)。这些轻量级线程减少了编写、维护和观察高吞吐量并发应用程序所需的工作量。

正如我的许多其他文章一样,在推出新功能之前,让我们先看看 Java 21 版本更新前的现状,以便更好地了解 Java 21 版本试图解决的问题以及好处是什么。

平台线程

在引入虚拟线程之前,java.lang.Thread 包已经支持所谓的平台线程。

这些线程通常以 1:1 的方式映射到操作系统调度的内核线程。操作系统线程相当“重”。这使得它们可以执行所有类型的任务。

根据操作系统和 JVM 启动参数配置的不同,一个平台线程默认消耗 1 MB 的空间。因此如果我们想在重负载高并发应用程序中使用一百万个线程,我们最好有超过 1 TB 的空闲内存!

如上所述,平台线程有一个明显的内存瓶颈限制了我们实际上可以拥有的线程数量。

每个请求一个线程

每个请求使用单个线程有很多优点,例如更容易的状态管理和清理。但它也造成了可扩展性限制。应用程序的“并发单元”(在本例中为请求)需要单个“并发平台单元”(在本例中也就是平台线程),但是在重负载高并发应用程序中,平台线程容易因为内存不足、CPU 资源耗尽而创建失败。

尽管“每个请求一个线程”有很多优点,平台线程可以更均匀地利用硬件,但我们还是需要一种完全不同的方法。

使用线程池

与在单个线程上处理以个请求不同,当任务完成时,线程会被线程池回收,因此另一个请求可能会重用相同的线程。这允许我们的程序使用更少的线程处理更多的请求,但会带来异步编程的负担。

异步编程具有自己的范例,具有一定的学习曲线,并且可能使我们的程序更难以理解和遵循。请求的每个部分可能在不同的线程上执行,在没有合理上下文的情况下创建堆栈跟踪,并使调试变得非常棘手甚至几乎不可能。

重新审视“每个请求一个线程”模型,很明显,我们需要一种更轻量级的线程方法来解决这个瓶颈,并最好按照我们熟悉的方式。

轻量级线程

由于平台线程的数量在不新增硬件资源的情况下无法改变,因此也就需要另一层抽象,以切断首先产生瓶颈的可怕的 1:1 映射。

轻量级线程不依赖于特定的平台线程,也不会为其分配大量内存。它们由运行时的 JVM 调度和管理而不是底层操作系统。这就是为什么可以创建大量轻量级线程的原因。

轻量级线程的概念并不新鲜,许多语言都有某种形式的轻量级线程:

  • Go 语言中的 Goroutines(协程)
  • Erlang 语言中的 Processes(轻量级进程)
  • Haskell Threads

Java 也在 21 版本中引入了自己的轻量级线程实现:虚拟线程

虚拟线程

虚拟线程是一个新的轻量级 java.lang.Thread 变体,是 Project Loom 项目的一部分,不由操作系统管理或调度。相反由 JVM 负责调度。当然在实际工作反映到操作系统还是以平台线程运行,但 JVM 正是利用所谓的载体线程(即平台线程)来“承载”虚拟线程,以便在需要时执行。

JVM / OS 线程调度示意图

所需的平台线程以 FIFO 工作方式在 ForkJoinPool 中进行管理,默认情况下,它使用所有可用的处理器,但可以通过调整系统属性 jdk.virtualThreadScheduler.parallelism 来根据我们的要求进行修改。我们熟悉的 ForkJoinPool 与并行流等其他功能使用的公共池之间的主要区别在于,公共池以 LIFO 模式运行。

物美价廉

虚拟线程是廉价且轻量级的,我们可以使用“每个请求一个线程”模型,而不必担心实际需要多少个线程。如果我们的代码在虚拟线程中调用阻塞 I/O 操作,则运行时会挂起这个被阻塞的虚拟线程,直到挂起结束后就可以恢复。这样一来,程序对硬件的利用就可以达到近乎最佳并提供高水平的并发性,从而实现高吞吐量。

因为虚拟线程非常便宜,所以虚拟线程不会被重用或需要被池化。每个任务都由其自己的虚拟线程来执行。

设定界限

JVM 调度程序通过载体线程来管理虚拟线程,因此需要一定的边界和分隔来确保可能的“无数”虚拟线程按预期运行。这是通过在载体线程和它可能承载的任何虚拟线程之间保持无线程关联来实现的:

  • 虚拟线程无法访问载体线程,Thread.currentThread() 返回虚拟线程本身。
  • 堆栈跟踪是独立的,虚拟线程中抛出的任何异常仅包含其自己的堆栈帧。
  • 虚拟线程的线程局部变量对其载体线程不可用,反之亦然。
  • 从代码的角度来看,载体线程及其虚拟线程对平台线程的共享是不可见的。

代码展示

在我看来,虚拟线程最好的事情之一就是我们不需要学习新的编程范例或复杂的新 API,就能够完成异步编程。在使用上,我们可以像对待平台线程一样对待虚拟线程。

创建平台线程

创建平台线程很简单,就像使用 Runnable 创建一样:

Runnable fn = () -> {
  // your code here
};

Thread thread = new Thread(fn).start();

随着 Project Loom 项目简化了新的并发方法,还提供了一种创建平台线程的新方法:

Thread thread = Thread.ofPlatform().
                      .start(runnable);

实际上,现在有一个完整的 Fluent API,因为 ofPlatform() 返回一个 Thread.Builder.OfPlatform 实例:

Thread thread = Thread.ofPlatform().
                      .daemon()
                      .name("my-custom-thread")
                      .unstarted(runnable);

但你来这里显然不是为了学习创建“旧”线程的新方法,你想要新的东西!

创建虚拟线程

对于虚拟线程,同样有一个 Fluent API:

Runnable fn = () -> {
  // your code here
};

Thread thread = Thread.ofVirtual(fn)
                      .start();

除了构建器方法之外,我们还可以直接使用以下命令创建虚拟线程:

Thread thread = Thread.startVirtualThread(() -> {
  // your code here
});

由于所有虚拟线程始终都是守护线程,因此如果我们想在主线程上等待虚拟线程执行完毕,可以调用 join() 方法。

创建虚拟线程的另一种方法是使用 Executor 类:

var executorService = Executors.newVirtualThreadPerTaskExecutor();

executorService.submit(() -> {
  // your code here
});

总结

尽管作用域值 (JEP 446) 和结构化并发 (JEP 453) 仍然是 Java 21 中的预览功能,但虚拟线程已经成为可投入生产的成熟功能。

虚拟线程是一种通用且强大的 Java 并发新方式,将对我们的未来程序产生重大影响。虚拟线程使用熟悉且可靠的“每个请求一个线程”方法,同时以最佳方式利用所有可用硬件,无需学习新范例或复杂的 API。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

标签:Java,21,Thread,平台,虚拟,线程,轻量级
From: https://www.cnblogs.com/waynaqua/p/17753808.html

相关文章

  • Java @Override 注解
    在代码中,你可能会看到大量的 @Override这个注解简单来说就是让编译器去读的,能够避免你在写代码的时候犯一些低级的拼写错误。Java @Override@Override用途@Override因为重写(Override)的特性是,参数和返回以及方法名都不能变。如果在写代码的时候,因为方法名不小心敲错了,在没有 @Ov......
  • Java @Override 注解
    在代码中,你可能会看到大量的 @Override 注解。这个注解简单来说就是让编译器去读的,能够避免你在写代码的时候犯一些低级的拼写错误。Java @Override 注解用来指定方法重写(Override),只能修饰方法并且只能用于方法重写,不能修饰其它的元素。@Override 注解可以强制一个子类必......
  • 入门篇-其之五-Java运算符(上)
    一元运算符之正负号Java支持多种一元运算符,一元运算符中的“一元”是指一个操作数。我们初中学过的正负号就属于一元运算符,因为正负号后面只有一个数字。正数使用+表示,其中+可以省略;负数使用-表示。如果变量的值是数值类型,也可以在变量前面加上正负号。/***正负号的表示*......
  • JavaScript
    1可以在任何位置,一般在body后<script>window.alert("JS");//浏览器弹出警告框document.write("HelloJS");//写入html页面中console.log("hellojs")//控制台输出vara=20;//声明全局变量var......
  • P7928 [COCI2021-2022#1] Kamenčići
    P7928[COCI2021-2022#1]Kamenčići[P7928COCI2021-2022#1]Kamenčići-洛谷|计算机科学教育新生态(luogu.com.cn)目录P7928[COCI2021-2022#1]Kamenčići题目大意思路code题目大意Alice和Bob又在玩游戏。在他们面前有\(n\)块石头排成一行,石头有红和蓝两种颜......
  • 基于Java的房产销售交易平台设计与实现
    开发语言:Java使用框架:springboot前端技术:JavaScript、VUE.js(2.X)、css3数据库:MySQL5.7数据库管理工具:Navicat或sqlyog开发工具:IDEA或Eclipse均可更多功能请看运行截图!......
  • Javaweb(五)
    1、MyBatisMyBatis是一款优秀的特久层框架,用于简化JDBC开发。持久层:①、负责将数据到保存到数据库的那一层代码。②、JavaEE三层架构:表现层、业务层、持久层。1.1、MyBatis快速入门1.2、Mapper代理开发使用步骤:然后添加核心配置文件的时候就可以使用包扫描的方式进行......
  • UAV2101~UAV2105编程与仿真51MCU初学者训练
    练习001:51单片机Proteus仿真:点亮一个灯1、器件清单Proteus关键词元器件CAP固定电容CAP-ELEC电解电容AT89C51AT89C51单片机CRYSTAL晶振BUTTON复位按键RES电阻RESPACK排阻LED-YELLOW黄色发光二极管2、电路3、代码#include<reg51.h>/......
  • 在JavaScript比较中,应该使用哪个等号运算符(== vs ===)?
    内容来自DOC[https://q.houxu6.top/?s=在JavaScript比较中,应该使用哪个等号运算符(==vs=)?](https://q.houxu6.top/?s=在JavaScript比较中,应该使用哪个等号运算符(vs===)?)我正在使用JSLint来检查JavaScript代码,并且它返回了许多建议,建议在if语句中比较idSele_UNVEHtype.value.......
  • P8511 [Ynoi Easy Round 2021] TEST_68
    题目传送门看到异或最大值,根据套路不妨考虑\(0-1trie\)。通过\(trie\)找到异或值最大的点对\((x,y)\)。那么除了\((x,y)\)到\(1\)路径上的点之外,其他的点的答案就是\((x,y)\)的异或值。接下来考虑怎么算出这\((x,y)\)到\(1\)路径上的点的答案,可以直接暴力计算!......