【译】虚拟线程:绝对优势

原文地址:Virtual Threads: A Definite Advantage

一、前言

深入了解虚拟线程如何提高应用程序的性能和可扩展性,同时将线程管理开销降到最低。

探索虚拟线程是一件很棒的事情,它是 Java 的一项强大功能,有望彻底改变多线程应用程序。在本文中,我们将深入探讨虚拟线程如何提高应用程序的性能和可扩展性,同时将线程管理的开销降到最低。让我们开始这段旅程,充分发挥虚拟线程的潜力!

在这里插入图片描述

为了证明这个用例,我们将创建一百万个平台线程和虚拟线程。生成这些线程后,我们将使用 HeapHero 和 fastThread 工具分析它们的堆和线程行为。通过这种探索,我们旨在突出平台线程和虚拟线程在性能上的区别。

二、什么是 Java 中的虚拟线程(VT)?

虚拟线程是一种不与特定操作系统线程绑定的线程创建方式。这对需要创建大量线程的应用程序非常有用,因为它可以减少创建和管理每个线程的开销。对于需要创建非临时线程的应用程序来说,它们也很有用,因为它们可以保证每个线程都有机会运行。

Java 21 引入了这一功能。虚拟线程也被称为 “绿色线程” 或 “轻量级线程”。它是线程的一种软件实现,使用操作系统的线程来实现并发。它们由 Java 虚拟机(JVM)管理,程序员无感知。

三、为什么虚拟线程很特别?

虚拟线程是一种特殊类型的线程,由平台线程创建,创建时只占用极少量的资源。由于具有这种功能,因此可以生成许多虚拟线程,用于多线程编程。

由于创建虚拟线程的成本很低,它不会像平台线程那样产生任何错误。虚拟线程的另一个优点是不需要像 Java 中的平台线程那样将它们池化。

四、平台线程(Platform Threads)

平台线程是 JDK 中的本地线程(java.lang.Thread)。

我们将使用 Java 中的线程类生成一百万个线程。在创建这些庞大线程的过程中,操作系统会变得极不稳定,并抛出 OutOfMemoryError。我们将在 Ubuntu Linux 中对这种行为进行实验。本实验使用的 JDK 版本为 21。

static int cnt = 1; 
public static void main(String[] args) { 
    for(int i = 0; i < 1000000; i++) {
        new Thread(
              new Runnable() {
                  @Override
                  public void run() {                         
                      try {                             
                          TimeUnit.HOURS.sleep(1);                         
                      } catch (Exception ex) {}
                  }
             }
         ).start();
        cnt++;
   }
}

在上面的代码中,我们在 for 循环中创建了一百万个线程,每个线程的休眠期最长可达 1 小时。线程休眠时,操作系统会缓存所有资源。在这种特殊情况下,操作系统需要将每个线程的所有资源保存较长时间,这是一项非常耗费资源的操作。请记住:在 Java 中创建线程是一项非常昂贵的操作。这就是在多线程编程模式下,应用程序启动时需要池化线程的原因。

很快,上述代码就会出现 OutOfMemory 错误。您可以在下图中看到该错误:

平台线程 OutOfMemoryError

五、虚拟线程(Virtual Threads)

现在,让我们来开发虚拟线程的代码。用例相同,但我们要在一个循环中动态生成一百万个虚拟线程。

static int cnt = 1; 
public static void main(String[] args) {   
      var executor = Executors.newVirtualThreadPerTaskExecutor();   
      IntStream.range(1, 1000000).forEach(i ->{       
        Future result =  executor.submit(() ->{           
          try {               
              TimeUnit.HOURS.sleep(1);           
          } catch (InterruptedException e) {               
             throw new RuntimeException(e);           
          }           
          String uuid = UUID.randomUUID().toString();       
       });       
       if(cnt == 999999) {           
             generateHeapDump(2);       
       }       
       cnt++;   
   }); 
}

在上面的代码中,通过调用 java.util.concurrent 包中 ExecutorService 类的 newVirtualThreadPerTaskExecutor() 方法,我们动态创建了一百万个虚拟线程。

在代码执行过程中,我们将使用 generateHeapDump() 方法获取堆内存快照。我们会在计数器达到 999999 时进行堆转储。这样,我们就能确保在堆内存日志中捕获最多的数据。

六、100万个虚拟线程居然没有OOM错误!

什么是 OutOfMemoryError?当应用程序没有足够的内存来处理事务时,系统就会抛出这个错误。那么,为什么在平台线程中会出现 OutOfMemoryError 而在虚拟线程中不会呢?

我们将借助下图进一步了解:

JVM 内存快照

内存有三个部分:堆(Heap)、元空间(Metaspace)和其他(Others)。就平台线程而言,线程栈存储在其他区域。

每个线程都有独立的内存,并存储在其他区域。为线程分配内存后,在进程结束后,应释放该内存。在平台线程方案中,线程等待时间为一小时,与之相关的内存不会很快释放。而且,新线程会再次生成,它们也要等待一小时。这样,就需要在 Others 区域分配大量内存,而 JVM 无法快速释放内存。因此,它就会抛出这个错误。

平台线程容易出现 OOM 错误的另外一个原因是,JDK5.0之后,默认线程栈内存是1M,这个内存分配量远超过虚拟线程方案中把虚拟线程当成对象来管理的方式。

在虚拟线程的情况下,虚拟线程被存储在堆区域,该区域由 JVM 进程控制,因为它们被视为对象。当你运行虚拟线程场景的代码时,你可以看到它会创建一百万个虚拟线程并休眠一小时。这些虚拟线程保存在堆内存中,生成线程所需的资源非常少。因此,在这种情况下不会抛出 OutOfMemoryError。

注意:有时,在虚拟线程的情况下,它也会抛出 OutOfMemoryError。这是因为当创建大量虚拟线程时,"堆内存"将被耗尽。但在上述情况下,它不会抛出 OOM 错误,因为默认内存足以容纳 100 万个虚拟线程!

我们将通过分析平台线程和虚拟线程的线程和内存行为来证实上述理论。

七、平台线程性能比较

我们将使用 fastThread 和 HeapHero 工具集,分别进行线程和堆转储分析,对平台线程性能进行比较研究。

7.1. 线程转储分析(Thread Dump Analysis)

这是 fastthread.io 为平台线程生成的线程转储报告。该报告非常智能,可提供发生 OutOfMemoryError 的可能性。

平台线程的线程转储报告

它显示 JVM 中有近 1600 个线程,这些数字令人震惊。报告的第一部分为我们提供了有关应用程序状态的足够信息。

现在,让我们用相同的堆栈跟踪快速检查线程。下图就是相关示意图。

堆栈跟踪相同的线程

该图显示了具有相同堆栈跟踪的多个线程。这些线程处于等待阶段。这是因为应用程序创建了大量线程,并要求这些线程等待一小时(平台线程代码请参考 Thread.sleep(…))。大约有 1600 个线程被要求等待。因此,报告显示的堆栈跟踪具有相同的行为。

不过,值得注意的是报告的其余部分。可以查看详细报告,以便您更好地查看和理解。

7.2. 堆转储分析

下面是使用 heaphero.io 对同一平台线程进行的堆转储分析。

堆转储分析的堆大小

可以看到,这里的堆大小要小得多。因此我们可以说,在平台线程的情况下,如果这些数字非常高,这很可能会给应用程序带来问题。可以查看 HeapHero 工具集对平台线程进行的故障排除报告。

八、虚拟线程性能比较

我们将使用 fastThread 和 HeapHero 工具集,分别进行线程和堆转储分析,对虚拟线程的性能进行比较研究。

8.1. 线程转储分析

下面是虚拟线程的线程转储分析。可以看到,线程数量约为 37 个。为什么会出现这种情况?为什么报告中没有显示所有这一百万个线程?

虚拟线程的线程转储分析

这是因为虚拟线程不被视为线程,所以在进行线程转储时,报告中不包括虚拟线程。这份线程转储情报报告会告诉你,堆的大小会增加。

8.2. 堆转储分析

现在,让我们使用 HeapHero 网站分析虚拟线程的报告。生成的报告可能有点笨重,您需要等待一段时间才能看到详细报告。

虚拟线程的堆转储分析

首先,请看一下报告,并在其中花点时间。报告显示有 999999 个 java.lang.VirtualThreads 实例。所有这些线程都从一个 jdk.internal.misc.CarrierThread 实例引用。

虚拟线程的堆报告

这份报告的有趣之处在于堆的大小为 401 MB。在执行与虚拟线程相关的代码时,JVM 会将这一百万个虚拟线程的所有信息保存到堆区。因此,在这种情况下,堆的大小非常大。这就是问题的关键所在。这些数据肯定也符合垃圾回收的条件。下面是堆分析报告,重点说明了这一点。

九、平台与虚拟线程性能比较

现在,让我们根据下表比较线程数与堆大小:

线程数堆大小线程分析报告堆内存分析报告
平台线程测试1599个之后报OutOfMemoryError1.85 MBPlatform Thread – Thread analysis reportPlatform Thread – Heap analysis report
虚拟线程测试100万个,没有问题401 MBVirtual Thread – Thread analysis reportVirtual Thread – Heap analysis report

当平台线程代码运行时,在抛出 OutOfMemoryError 之前会产生近 1600 个线程。但在这种情况下,堆的大小相对较小。这是因为,正如本文前文所述,线程栈保存在 Others 区域内,而不是堆内。

在虚拟线程的情况下,应用程序创建的线程数量相对较少,但堆的大小却非常大。这是因为虚拟线程使用了堆内存。

十、结论

虚拟线程是创建多线程应用程序的有用工具。通过使用多个线程并行执行任务,虚拟线程可以提高应用程序的性能。虚拟线程的使用方法与多线程应用程序中使用平台线程的方法相同。在创建和管理每个线程时没有任何开销,但仍能产生更好的效果。这是 Java 语言的一项强大功能,有了这项功能,应用程序的扩展就变得非常容易。这是使用虚拟线程的一个明显优势。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【星戈瑞】Sulfo-CY3 DBCO荧光光谱特性之吸收、发射光谱

Sulfo-CY3 DBCO的荧光光谱特性通常涵盖了其吸收和发射光谱。这些光谱特性是研究该染料在生物分子标记和成像中的应用时的参数。 吸收光谱&#xff1a; Sulfo-CY3 DBCO的吸收光谱通常显示了其在不同波长下吸收光的能力。典型情况下&#xff0c;Sulfo-CY3 DBCO的吸收峰位于可见光…

单向通信----一对一聊天

package 一对一聊天; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import …

CRM客户关系管理系统的主要功能有哪些?

我们都知道&#xff0c;CRM系统可以帮助企业加快业务增长。如果一个企业能提高业务效率、跨团队协作、有效管理客户、轻松共享和同步数据&#xff0c;那么企业竞争力将极大地提高。基于此&#xff0c;我们说说CRM客户关系管理系统的主要功能分析。 完整的CRM是什么样的&#x…

HarmonyOS开发(十):通知

1、通知概述 1.1、简介 应用可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知使用的的常见场景&#xff1a; 显示接收到的短消息、即使消息...显示应用推送消息显示当前正在进行的事件&#xff0c…

最常见的直流负载工作方式

直流负载工作方式是指在电力系统中&#xff0c;直流电源为负载提供电能的方式。在实际应用中&#xff0c;直流负载工作方式有很多种。 直接供电方式&#xff1a;这是最简单的直流负载工作方式&#xff0c;即直流电源直接为负载提供电能。这种方式适用于负载较小、对电源稳定性要…

【Vue第1章】Vue核心

目录 1.1 Vue简介 1.2 初识Vue 1.2.1 代码 1.3 模板语法 1.3.1 效果 1.3.2 模板的理解 1.3.3 插值语法 1.3.4 指令语法 1.3.5 代码 1.4 数据绑定 1.4.1 效果 1.4.2 单向数据绑定 1.4.3 双向数据绑定 1.4.4 代码 el与data的两种写法 代码 1.5 MVVM模型 1.5.1 …

Java基础-代码块及其细节

代码块概念&#xff1a; 注意调用时机 好处与使用场景 将构造器的冗余部分提取到代码块 每个构造器执行时都会先执行代码块 静态代码块与普通代码块的区别 注意&#xff1a;创建对象实例时&#xff0c;静态代码块只会被调用一次 例子 public Class DD{static{//打印"…

Qt开发学习笔记02

将窗口设为提示框 Qt::ToolTipQt 数据库连接池 #ifndef SQLITE_H #define SQLITE_H#include <QSqlDatabase> #include <QSqlError> #include <QSqlQuery> #include <QQueue> #include <QMutex> #include <QDebug> #include "../con…

翻译: 生成式人工智能的经济潜力 第一部分商业价值 The economic potential of generative AI

生成式人工智能即将引发下一波生产力的浪潮。我们首先看看业务价值可能会增加的地方&#xff0c;以及对劳动力的潜在影响。 1. 人工智能已经逐渐渗透到我们的生活中 人工智能已经逐渐渗透到我们的生活中&#xff0c;从为智能手机提供动力的技术到汽车上的自动驾驶功能&#x…

实现简易的多人聊天

服务端 import java.io.*; import java.net.*; import java.util.ArrayList; public class Server{public static ServerSocket server_socket;public static ArrayList<Socket> socketListnew ArrayList<Socket>(); public static void main(String []args){try{…

SpringBoot Maven打包插件spring-boot-maven-plugin无法解析离谱原因记录

目录 一、常见打包插件无法解析原因二、打包插件无法解析离谱原因三、总结 打包配置 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><…

基于深度学习路径规划RRT*-训练图像预处理

基于深度学习路径规划RRT*-训练图像预处理 图像预处理说明 在基于采样的RRT算法对机器人进行路径规划时&#xff0c;由于采样点的随机性&#xff0c;会增加路径的搜索时间的路径的非最优性&#xff0c;所以基于神经网络的优势&#xff0c;利用深度学习进行RRT的随机采样&…

M2芯片回顾

M芯片&#xff0c; 一竟到底&#xff1a; M1芯片的体积&#xff1a; M2 代表 M 系列芯片的第二代&#xff1a; 其进一步提升了芯片的性能和功率 &#xff0c;这也是 M 芯片目前的追求&#xff1a;最大化性能的同时&#xff0c;最大限度降低功耗。 UMA 统一内存架构被再一次提到…

【开源】基于Vue+SpringBoot的智慧家政系统

项目编号&#xff1a; S 063 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S063&#xff0c;文末获取源码。} 项目编号&#xff1a;S063&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询家政服…

三种基于路径跟踪的位相解包裹算法比较

目录 1. 枝切法(Branch Cut&#xff0c;简称 BC) 2 质量图导向的路径跟踪算法(Quality Guide&#xff0c;简称QG) 3 菱形算法(Rhombus Alogrithm&#xff0c;简称 RA) 1. 枝切法(Branch Cut&#xff0c;简称 BC) 美国的 JPL实验室的 Goldstein和 Zebker等人在1986年提出的枝…

Numpy 实现基尼指数算法的决策树

基尼系数实现决策树 基尼指数 Gini ⁡ ( D ) 1 − ∑ k 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 \operatorname{Gini}(D)1-\sum_{k1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2} Gini(D)1−k1∑K​(∣D∣∣Ck​∣​)2 特征 A A A条件下集合 D D D的基尼指数&#xff1a; Gi…

97基于matlab的改进的带记忆的模拟退火算法求解TSP问题

基于matlab的改进的带记忆的模拟退火算法求解TSP问题&#xff0c;采用多普勒型降温曲线描述迭代过程&#xff0c;在传统算法的基础上增加记忆功能&#xff0c;可测试中国31/64/144以及att48城市的数据&#xff0c;也可自行输入数据进行测试&#xff0c;测试结果基本达到当前最优…

Java - InetAddress#isReachable 方法解析

文章目录 前言代码资源 前言 在 Java 中&#xff0c;InetAddress 类提供一个方法来检查一个网络地址是否可达&#xff0c;其作用类似与在命令行执行 ping 命令&#xff0c; 这个方法就是 isReachable 方法。 代码 var baidu InetAddress.getByName("www.baidu.com&quo…

点评项目——短信登陆模块

2023.12.6 短信登陆如果基于session来实现&#xff0c;会存在session共享问题&#xff1a;多台Tomcat不能共享session存储空间&#xff0c;这会导致当请求切换到不同服务器时出现数据丢失的问题。 早期的解决办法是让session提供一个数据拷贝的功能&#xff0c;即让各个Tomcat的…

封装校验规则(以及复选框和整体校验)-----Vue3+ts项目

登录校验页面 <script setup lang"ts"> import { ref } from vue import { mobileRules, passwordRules } from /utils/rules const mobile ref() const password ref() </script><!-- 表单 --><van-form autocomplete"off">&l…