Java 21 虚拟线程:使用指南(一)

这篇具有很好参考价值的文章主要介绍了Java 21 虚拟线程:使用指南(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

虚拟线程是由 Java 21 版本中实现的一种轻量级线程。它由 JVM 进行创建以及管理。虚拟线程和传统线程(我们称之为平台线程)之间的主要区别在于,我们可以轻松地在一个 Java 程序中运行大量、甚至数百万个虚拟线程。

由于虚拟线程的数量众多,也就赋予了 Java 程序强大的力量。虚拟线程适合用来处理大量请求,它们可以更有效地运行 “一个请求一个线程” 模型编写的 web 应用程序,可以提高吞吐量以及减少硬件浪费。

由于虚拟线程是 java.lang.Thread 的实现,并且遵守自 Java SE 1.0 以来指定 java.lang.Thread 的相同规则,因此开发人员无需学习新概念即可使用它们。

但是虚拟线程才刚出来,对我们来说有一些陌生。由于 Java 历来版本中无法生成大量平台线程(多年来 Java 中唯一可用的线程实现),已经让程序员养成了一套关于平台线程的使用习惯。这些习惯做法在应用于虚拟线程时会适得其反,我们需要摒弃。

此外虚拟线程和平台线程在创建成本上的巨大差异,也提供了一种新的关于线程使用的方式。Java 的设计者鼓励使用虚拟线程而不必担心虚拟线程的创建成本。

本文无意全面涵盖虚拟线程的每个重要细节,目的只是提供一套介绍性指南,以帮助那些希望开始使用虚拟线程的人充分利用它们。

本文完整大纲如下,

Java 21 虚拟线程:使用指南(一)

请大方使用同步阻塞 IO

虚拟线程可以显着提高以 “一个请求一个线程” 模型编写的 web 应用程序的吞吐量(注意不是延迟)。在这种模型中,web 应用程序针对每个客户端请求都会创建一个线程进行处理。因此为了处理更多的客户端请求,我们需要创建更多的线程。

在 “一个请求一个线程” 模型中使用平台线程的成本很高,因为平台线程与操作系统线程对应(操作系统线程是一种相对稀缺的资源),阻塞了平台线程,会让它无事可做一直处于阻塞中,这样就会造成很大的资源浪费。

然而,在这个模型中使用虚拟线程就很合适,因为虚拟线程非常廉价就算被阻塞也不会造成资源浪费。因此在虚拟线程出来后,Java 的设计者是建议我们应该以简单的同步风格编写代码并使用阻塞 IO。

举个例子,以下用非阻塞异步风格编写的代码是不会从虚拟线程中受益太多的,

CompletableFuture.supplyAsync(info::getUrl, pool)
   .thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofString()))
   .thenApply(info::findImage)
   .thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofByteArray()))
   .thenApply(info::setImageData)
   .thenAccept(this::process)
   .exceptionally(t -> { t.printStackTrace(); return null; });

另一方面,以下用同步风格并使用阻塞 IO 编写的代码使用虚拟线程将受益匪浅,

try {
   String page = getBody(info.getUrl(), HttpResponse.BodyHandlers.ofString());
   String imageUrl = info.findImage(page);
   byte[] data = getBody(imageUrl, HttpResponse.BodyHandlers.ofByteArray());
   info.setImageData(data);
   process(info);
} catch (Exception ex) {
   t.printStackTrace();
}

并且上面的同步代码也更容易在调试器中调试、在分析器中分析或通过线程转储进行观察。要观察虚拟线程,可以使用 jcmd 命令创建线程转储,

jcmd <pid> Thread.dump_to_file -format=json <file>

用同步风格并使用阻塞 IO 风格编写的代码越多,虚拟线程的性能和可观察性就越好。而用异步非阻塞 IO 风格编写的程序或框架,如果每个任务没有专用一个线程,则无法从虚拟线程中获得显着的好处。

使用虚拟线程,我们因该避免将同步阻塞 IO 与异步非阻塞 IO 混为一谈。

避免池化虚拟线程

关于虚拟线程使用方面最难理解的一件事情就是,我们不应该池化虚拟线程。虽然虚拟线程具有与平台线程相同的行为,但虚拟线程和线程池其实是两种概念。

平台线程是一种稀缺资源,因为它很宝贵。越宝贵的资源就越需要管理,管理平台线程最常见的方法是使用线程池。

不过在使用线程池后,我们需要回答的一个问题,线程池中应该有多少个线程?最小线程数、最大线程数应该设置多少?这也是一个问题。

虚拟线程是一种非常廉价的资源,每个虚拟线程不应代表某些共享的、池化的资源,而应代表单一任务。在应用程序中,我们应该直接使用虚拟线程而不是通过线程池使用它。

那么我们应该创建多少个虚拟线程嘞?答案是不必在乎虚拟线程的数量,我们有多少个并发任务就可以有多少个虚拟线程。

如下是一段提交任务的代码,将每个任务都提交到线程池中执行,在 Java 21 以后,不建议再使用共享线程池执行器,代码如下,

Future<ResultA> f1 = sharedThreadPoolExecutor.submit(task1);
Future<ResultB> f2 = sharedThreadPoolExecutor.submit(task2);
// ... use futures

建议使用虚拟线程执行器,代码如下,

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
   Future<ResultA> f1 = executor.submit(task1);
   Future<ResultB> f2 = executor.submit(task2);
   // ... use futures
}

上面代码虽然仍使用 ExecutorService,但从 Executors.newVirtualThreadPerTaskExecutor() 方法返回的执行器不再使用线程池。它会为每个提交的任务都创建一个新的虚拟线程。

此外,ExecutorService 本身是轻量级的,我们可以像创建任何简单对象一样直接创建一个新的 ExecutorService 对象而不必考虑复用。

这使我们能够依赖 Java 19 中新添加的 ExecutorService.close() 方法和 try-with-resources 语法糖。在 try 块末尾隐式调用 ExecutorService.close() 方法,会自动等待提交给 ExecutorService 的所有任务(即 ExecutorService 生成的所有虚拟线程)终止。

对于广播场景来说,使用 Executors.newVirtualThreadPerTaskExecutor() 比较合适,在这种场景中,希望同时对不同的服务执行多个传出调用,并且方法结束时就关闭线程池,代码如下,

void handle(Request request, Response response) {
    var url1 = ...
    var url2 = ...

    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        var future1 = executor.submit(() -> fetchURL(url1));
        var future2 = executor.submit(() -> fetchURL(url2));
        response.send(future1.get() + future2.get());
    } catch (ExecutionException | InterruptedException e) {
        response.fail(e);
    }
}

String fetchURL(URL url) throws IOException {
    try (var in = url.openStream()) {
        return new String(in.readAllBytes(), StandardCharsets.UTF_8);
    }
}

针对广播模式和其他常见的并发模式,如果希望有更好的可观察性,建议使用结构化并发。这是 Java 21 中新出的特性,这里给大家卖个关子,我将在后续进行讲解。

根据经验来说,如果我们的应用程序从未经历 1 万的并发访问,那么它不太可能从虚拟线程中受益。一方面它负载太轻而不需要更高的吞吐量,一方面并发请求任务也不够多。

参考资料

  • https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-E695A4C5-D335-4FA4-B886-FEB88C73F23E

最后说两句

针对虚拟线程的使用,相信大家心里已经有了答案。虚拟线程不同于平台线程,它非常廉价,Java 的设计者鼓励我们直接使用虚拟线程,而无需池化,也不必担心过多的虚拟现场会影响性能。

事实上,虚拟现场就是为了解决同步阻塞 IO 对硬件的资源利用率不够高这一问题。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!文章来源地址https://www.toymoban.com/news/detail-763090.html

到了这里,关于Java 21 虚拟线程:使用指南(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java21上手体验-分代ZGC和虚拟线程

    几天前Oracle刚刚发布了Java21, 由于这是最新的LTS版本,引起了大家的关注。 我也第一时间在个人项目中进行了升级体验。 一探究竟,和大家分享。 https://jdk.java.net/21/release-notes https://my.oschina.net/waylau/blog/10112170 JEP 431:序列集合 JEP 439:分代 ZGC JEP 440:记录模式 JEP 441:switc

    2024年02月08日
    浏览(36)
  • Java 21 新特性:虚拟线程(Virtual Threads)

    在Java 21中,引入了虚拟线程(Virtual Threads)来简化和增强并发性,这使得在Java中编程并发程序更容易、更高效。 虚拟线程,也称为“用户模式线程(user-mode threads)”或“纤程(fibers)”。该功能旨在简化并发编程并提供更好的可扩展性。虚拟线程是轻量级的,这意味着它

    2024年02月08日
    浏览(35)
  • Java 21 正式 GA,虚拟线程真的来了

    UTC 时间 2023 年 9 月 19 日,期盼已久的 Java 21 终于发布正式版! 本文一起来看看其中最受 Java 开发者关注的一项新特性:Loom 项目的两个新特性之一的 ”虚拟线程(Virtual Thread)“(另外一个新特性是 ”结构化并发(Structured Concurrency)“,当前是预览状态),它被称之为 J

    2024年02月08日
    浏览(56)
  • Java 21 虚拟线程如何限流控制吞吐量

    虚拟线程(Virtual Threads)是 Java 21 所有新特性中最为吸引人的内容,它可以大大来简化和增强Java应用的并发性。但是,随着这些变化而来的是如何最好地管理此吞吐量的问题。本文,就让我们看一下开发人员在使用虚拟线程时,应该如何管理吞吐量。 在大多数情况下,开发

    2024年02月20日
    浏览(46)
  • 【Java】JDK 21中的虚拟线程以及其他新特性

      目录 一、字符串模板(String Templates) 二、序列化集合(Sequenced Collections) 三、分代ZGC(Generational ZGC) 四、记录模式(Record Patterns) 五、Fibers(纤程) 结论 JDK 21是Java开发工具包的最新版本,它引入了许多令人振奋的新特性,旨在提高开发人员的生产力和代码质量。在本

    2024年02月08日
    浏览(44)
  • 使用VirtualBox安装Linux虚拟机,避坑指南

      记录一下自己学习Linux系统的过程。因为本人一直使用的是Windows系统,所以需要装一个虚拟机来创建一个基于Linux内核的操作系统。主要分为以下两个不步骤:第一步,先安装虚拟机软件,我用的是VirtualBox,使用其他的也可以;第二步就是用虚拟机软件安装Linux系统。  

    2023年04月24日
    浏览(58)
  • jdk21 虚拟线程原理及使用分享

    jdk21已于北京时间9月19日21点正式发布, 其中引人注目的就是虚拟线程(Virtual Thread)随之正式发布, 不再是此前jdk19、jdk20中的预览版本。 平台线程 :java传统的线程是对系统线程的包装,为了区别于虚拟线程,因此将通过传统方式实现的线程叫做平台线程(Platform Thread) 虚拟线程

    2024年02月04日
    浏览(37)
  • 《cuda c编程权威指南》04 - 使用块和线程索引映射矩阵索引

    目录 1. 解决的问题 2. 分析 3. 方法 4. 代码示例 利用块和线程索引,从全局内存中访问指定的数据。 通常情况下, 矩阵 是用行优先的方法在 全局内存 中线性存储的。如下。 8列6行矩阵(nx,ny)=(8,6)。 这里建立二维网格(2,3)+二维块(4,2)为例,使用其块和线程索引映射矩阵

    2024年02月14日
    浏览(43)
  • Node.js 版本管理工具 n 使用指南

    Node.js 版本更新很快,目前 node v20.x 已经发布,我们在使用时避免不了会需要切换不同的 Node.js 的版本来使用不同版本的特性。 所以就出现了像 windows 上的 nvm ,MacOS 上的 n 工具,本文就介绍一下如何使用 n 管理 Node.js 的版本。 使用 Brew 安装时,未安装可以参考 Brew 官网安装

    2024年02月16日
    浏览(54)
  • Open3D Python版本快速安装和使用指南

    Open3D Python版本快速安装和使用指南 Open3D是一个开源的3D计算机视觉库,提供了一系列用于处理和分析3D数据的功能,包括点云、几何体、图像等。本文将介绍如何在Python环境中快速安装和使用Open3D库,并提供相应的源代码示例。 第一步:安装Python和pip 首先,确保你的系统已

    2024年02月07日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包