Java21-虚拟线程小试牛刀-meethigher

其他语言,如Go早期就支持了叫做协程的东西,它是轻量化后的线程,而Java异步编程却只有线程的概念。JDK8以后的升级带来的改变总体感觉不大,不过这次JDK21带来的Virtual Thread还是值得体验一把的,可以说是YYDS,终于有理由不使用Java8了!

首先下载JDK 21。

如官方所说,Virtual Thread在JDK19和JDK20时,还是预览版本。在JDK21才正式确定出道。因此现有版本,已经可以正式使用了。

下面所有的Virtual Thread我都叫他虚拟线程了,而不是协程,反正只是个名。

不过新版本发布之后,想要正式使用,还需要等待IDE更新,不然使用体验没那么好。

以下测试都是通过JDK原生编译命令执行。

一、快速入门

1.1 如何创建

Java的虚拟线程,是基于ForkJoinPool线程池实现的,它适用于密集型阻塞场景。

常规情况下,如果存在阻塞,那么线程就会卡在那里了,这段时间是啥也不干,但却又占着茅坑。其实这部分时间还是可以让他做别的事情的,就像netty的事件驱动非阻塞一样,于是虚拟线程应运而生。

说人话就是,虚拟线程适合处理大量阻塞的任务。如果处理计算任务,或者个数较少的阻塞任务,优势并不明显。

Java中的new Thread()获取到的即对应操作系统中的线程。不过在JDK21中,给了他更明确的概念,平台线程PlatformThread。

不求甚解,只求会用。至于如何创建PlatformThread和VirtualThread,请看以下代码。

//线程,即平台线程。两种方式
Thread platformThread = new Thread(new TestRunner(null));
Thread platformThread1 = Thread.ofPlatform().unstarted(new TestRunner(null));
//虚拟线程。跟一下源码,可知他是依赖于池化的ForkJoinPool的
Thread virtualThread = Thread.ofVirtual().unstarted(new TestRunner(null));

1.2 性能比较

下面比较PlatformThread和VirtualThread处理密集型阻塞任务时的执行性能。

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;public class Main {/*** 基于15个线程池实现的虚拟线程* 执行一万个任务,每个任务耗时1000毫秒,总共耗费2637毫秒*/public static void virtualThread(int count) throws Exception {StopWatcher stopWatcher = new StopWatcher();stopWatcher.start();CountDownLatch countDownLatch = new CountDownLatch(count);for (int i = 0; i < count; i++) {Thread.ofVirtual().start(new TestRunner(countDownLatch));}countDownLatch.await();stopWatcher.stop();System.out.printf("本次执行耗时:%s毫秒", stopWatcher.getTimeInterval().toMillis());}/*** 基于15个池化线程* 执行一万个任务,每个任务耗时1000毫秒,总共耗费11分钟*/public static void platformThread(int count) throws Exception {StopWatcher stopWatcher = new StopWatcher();stopWatcher.start();CountDownLatch countDownLatch = new CountDownLatch(count);for (int i = 0; i < count; i++) {CompletableFuture.runAsync(new TestRunner(countDownLatch));}countDownLatch.await();stopWatcher.stop();System.out.printf("本次执行耗时:%s毫秒", stopWatcher.getTimeInterval().toMillis());}public static void main(String[] args) throws Exception {int count = 10000;//virtualThread(count);platformThread(count);}public static class TestRunner implements Runnable {private final CountDownLatch countDownLatch;public TestRunner(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {try {System.out.println(Thread.currentThread() + " start " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));Thread.sleep(1000);System.out.println(Thread.currentThread() + " stop " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));countDownLatch.countDown();} catch (Exception e) {System.out.println(e.getMessage());}}}public static class StopWatcher {private long start;private long stop;public StopWatcher() {}public void start() {this.start = System.currentTimeMillis();}public void stop() {this.stop = System.currentTimeMillis();}public Duration getTimeInterval() {return Duration.ofMillis(this.stop - this.start);}}
}

进行编译,并运行。

javac Main.java && java Main

两种方式,分别模拟处理10000个阻塞任务,每个任务阻塞1秒。

  • PlatformThread: 15个池化线程
  • VirtualThread: 15个池化线程,但是采用了虚拟线程方式

我的硬件情况就不详细描述了,直接对比结果,就能清楚明了感受到差异。

执行结果

  1. 耗时对比
    • PlatformThread: 耗时11分钟
    • VirtualThread:耗时2秒
  2. CPU使用率对比
    • PlatformThread: 占用10%左右
    • VirtualThread: 占用50%左右

综上,处理密集型阻塞任务,使用VirtualThread更大程度发挥了CPU性能!

此处官方已经明确说了,虚拟线程只适合密集型阻塞场景。假如像计算型,反而会降低性能。

说白了,虚拟线程就是压榨CPU空闲的时间,不允许他闲下来。这点跟操作系统的时间片、Netty的事件驱动类似。

二、实际案例

2.1 购物

请看如下代码

像findUserByName和loadCardFor是通过数据库查询,其实在查询的过程中,将请求发给数据库,等待数据库响应的过程就是阻塞的。

这种顺序执行的情况,其中就存在CPU利用不充分的问题,就可以使用异步编程提升性能。但是采用多线程能提高性能吗?

先分析下业务,这是一个购物过程。

  1. 用户:查询并获取用户
  2. 购物车:通过用户查询并获取购物车,获取购物车的总价格
  3. 订单:支付该用户的总价格对应的费用,获取订单
  4. 通知:通知用户订单信息

会发现这里面是环环相扣的,没有并行的业务。

即使我们把代码进行了异步如下,有意义吗?没意义!

假如同时来100个请求,会发现,阻塞时间的总量根本没变。性能并没有提升。

2.1 购物-优化版

那么如何提升性能?就得通过阻塞入手了,让他变成不阻塞。这样单位时间内处理的请求就更多了。

而且,也不能采用上述异步后的代码形式,因为他难以阅读、难以调试。

我们希望他

  1. 不阻塞
  2. 易阅读、易调试

那么如何优化呢?请看如下代码。

private ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();public void pay(String name) throws Exception {executor.submit(() -> {User user = userService.findUserByName(name);if (!repo.contains(user)) {repo.save(user);}var cart = cartService.loadCartFor(user);var total = cart.items().stream().mapToInt(Item::price).sum();var transactionId = paymentService.pay(user, total);emailService.send(user, cart, transactionId);})
}

使用虚拟线程,既能易调试、易阅读,而且将原来阻塞的时间用来处理更多的请求。**这些内部执行过程,都由Java自行处理,不需要开发者关心。**用老外的话说,”这不是魔术,这只是工程化“。

如果不理解,建议将1.2的代码亲自调试一下。

三、参考致谢

JEP 444: Virtual Threads

Java 21 new feature: Virtual Threads #RoadTo21 - YouTube

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法题:203. 移除链表元素(递归法、设置虚拟头节点法等3种方法)Java实现创建链表与解析链表

1、算法思路 讲一下设置虚拟头节点的那个方法&#xff0c;设置一个新节点指向原来链表的头节点&#xff0c;这样我们就可以通过判断链表的当前节点的后继节点值是不是目标删除值&#xff0c;来判断是否删除这个后继节点了。如果不设置虚拟头节点&#xff0c;则需要将头节点和后…

c++装饰器模式

前言 装饰器模式&#xff0c;就是可以对一个对象无限装饰一些东西&#xff0c;而且可以没有顺序。比如一个人可能只会说出他的名字&#xff0c;但是可以让他再说哈哈&#xff0c;可以说完哈哈之后再说哇哇。如何后面又不想装饰了&#xff0c;不需要改类原来的代码&#xff0c;…

VB.NET—窗体引起的乌龙事件

目录 前言: 过程: 总结: 升华: 前言: 分享一个VB.NET遇到的一个问题&#xff0c;开始一直没有解决&#xff0c;这个问题阻碍了很长时间&#xff0c;成功的变成我路上的绊脚石&#xff0c;千方百计的想要绕过去&#xff0c;但事与愿违怎么也绕不过去&#xff0c;因为运行不了…

基于白冠鸡算法的无人机航迹规划-附代码

基于白冠鸡算法的无人机航迹规划 文章目录 基于白冠鸡算法的无人机航迹规划1.白冠鸡搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用白冠鸡算法来优化无人机航迹规划。 1.白冠鸡…

Linux实现简易shell

文章目录 &#x1f984;0. shell&#x1f42e;1. 交互及获取命令行&#x1f437;2. 解析命令行&#x1f42f;3. 执行命令行&#x1f405;3.1 普通命令&#x1f405;3.2 内建命令 &#x1f981;4. 主函数逻辑及演示 本章代码gitee仓库&#xff1a;简易shell &#x1f984;0. she…

K8S知识点(二)

&#xff08;1&#xff09;K8S概念 K8S是通过控制pod来控制容器进而控制程序的 service是沟通Pod和外键的桥梁&#xff0c;可以实现负载均衡的效果&#xff0c;加权负载的效果 &#xff08;2&#xff09;环境搭建-环境规划 &#xff08;3&#xff09;环境搭建-主机安装 使用…

C++ 代码实例:并查集简单创建工具

文章目录 前言代码仓库代码说明main.cppMakefile 结果总结参考资料作者的话 前言 C 代码实例&#xff1a;并查集简单创建工具。 代码仓库 yezhening/Programming-examples: 编程实例 (github.com)Programming-examples: 编程实例 (gitee.com) 代码 说明 简单地创建并查集注…

<Vue>使用依赖注入的方式共享数据

什么是vue依赖注入&#xff1f; Vue是一个用于构建用户界面的渐进式框架。 它提供了一种简单而灵活的方式来管理组件之间的数据流&#xff0c;即依赖注入&#xff08;Dependency Injection&#xff0c;DI&#xff09;。 依赖注入是一种设计模式&#xff0c;它允许一个组件从另一…

HTTPS的加密方式超详细解读

在了解https的加密方式之前&#xff0c;我们需要先行了解两个特别经典的传统加密方式&#xff1a; 1、对称加密 1.1、定义 需要对加密和解密使用相同密钥的加密算法。所谓对称&#xff0c;就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解…

【技术干货】开源库 Com.Gitusme.Net.Extensiones.Core 的使用

目录 1、项目介绍 2、为项目添加依赖 3、代码中导入命名空间 4、代码中使用 示例 1&#xff1a;string转换 示例 2&#xff1a;object转换 1、项目介绍 Com.Gitusme.Net.Extensiones.Core是一个.Net扩展库。当前最新版本1.0.4&#xff0c;提供了常见类型转换&#xff0c…

<蓝桥杯软件赛>零基础备赛20周--第4周--杂题-1

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…

统计学习方法 条件随机场

文章目录 统计学习方法 条件随机场随机场马尔可夫随机场定义因子分解 条件随机场定义参数化形式简化形式矩阵形式 概率预测问题前向-后向算法概率的计算期望值的计算 学习问题改进的迭代尺度法拟牛顿法 解码问题 统计学习方法 条件随机场 学习李航的《统计学习方法》时&#x…