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

news2024/11/27 6:36:36

其他语言,如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;
        }

        @Override
        public 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.coloradmin.cn/o/1175519.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux学习笔记之三(vim编辑器)

目录 1、vim的四种工作模式2、正常模式下的操作指令2.1、进入编辑模式2.2、进入命令行模式2.3、进入视觉模式2.4、光标跳转2.5、复制、粘贴、删除2.6、重复、撤回操作 3、命令行模式下的操作指令 1、vim的四种工作模式 vim的四种工作模式分别是编辑模式(insert mode)、命令行模…

蓝桥杯官网填空题(方格计数)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 如下图所示&#xff0c;在二维平面上有无数个 11 的小方格。 我们以某个小方格的一个顶点为圆心画一个半径为 50000 的圆。 你能计算出这个圆里有多少个完整的小方…

java入门-JDK下载与安装

1、下载jdk Java 的产品叫JDK&#xff08;Java Development Kit: Java开发者工具包&#xff09;&#xff0c;必须安装JDK才能使用java 1、官网地址 https://www.oracle.com/java/ https://www.oracle.com/java/technologies/downloads/ 目前比较稳定的版本为 JDK17. 我们就安…

python 之 lamda 函数

文章目录 示例1. 基本使用2. 与内置函数结合使用 注意事项&#xff1a; 当谈到Python的lambda函数&#xff0c;它代表一种匿名函数的能力。与普通函数不同&#xff0c;lambda函数是一种单行函数&#xff0c;通常用于代码更简洁的场景&#xff0c;不需要对函数进行命名。其语法如…

康耐视深度学习ViDi-Plugins菜单介绍

Manage Plugins ----------- 插件管理 能够在此窗口界面看到一些关于插件的信息&#xff0c;也能够在此窗口界面添加或移除(*.dll)文件。 注意&#xff1a; 在没有专业人员指导的情况下禁止操作添加和移除(*.dll)文件&#xff0c;错误的操作有可能会造成软件的功能丧失更严重…

python 之enumerate 函数

文章目录 enumerate() 函数的语法返回值使用示例&#xff1a;示例 1&#xff1a;基本使用示例 2&#xff1a;指定起始索引值 注意事项&#xff1a; enumerate() 是 Python 中一个非常有用的内置函数&#xff0c;用于在迭代迭代器&#xff08;例如列表、元组、字符串或其他可迭代…

前沿重器[37] | 大模型对任务型对话的作用研究

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…

Makefile 保姆级使用教程

目录 Makefile 规则 Makefile的使用介绍 make 命令的使用 即时变量、延时变量介绍和使用 使用make命令编译多个文件 假想目标 常用函数 1.$(foreach var,list,text) 2.$(wildcard pattern) 3.$(filter pattern...,text) 4.$(filter-out pattern...,text) 5.$(patsub…

【每日一题】最大单词长度乘积

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;转态压缩 写在最后 Tag 【位运算-状态压缩】【字符串】【2023-11-06】 题目来源 318. 最大单词长度乘积 题目解读 找出英文字符串数组中两个字符串长度乘积的最大值&#xff0c;并且这两个字符串不含公共字母。 解题…

Day21力扣打卡

打卡记录 在树上执行操作以后得到的最大分数&#xff08;树状DP&#xff09; 链接 大佬的题解 class Solution { public:long long maximumScoreAfterOperations(vector<vector<int>> &edges, vector<int> &values) {vector<vector<int>…

更强的端点控制如何保护系统和数据

端点——员工日常使用的笔记本电脑、设备和工作站——正在成为网络攻击者的目标。 如果他们可以仅通过一个端点访问组织的系统&#xff0c;那么他们就拥有一个启动板&#xff0c;可以从该启动板在网络上横向移动&#xff0c;以窃取数据或植入勒索软件等。 他们甚至可以提升权…

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

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

Java与Redis的集成

目录 一、Java连接Redis 1.1 导入依赖 1.2 Redis服务器准备 1.3 建立连接 二、Java操作Redis的常见类型数据存储 2.1 String&#xff08;字符串&#xff09;存取值操作 2.1.1 指定存储数据的数据库与存储入库操作 2.1.2 存储数据的修改与查询 2.1.3 数据临时存储 2.2…

Python unittest单元测试框架 TestSuite测试套件

TestSuite 测试套件简介 对一个功能的验证往往是需要很多多测试用例&#xff0c;可以把测试用例集合在一起执行&#xff0c;这就产生了测试套件TestSuite 的概念&#xff0c;它是用来组装单个测试用例&#xff0c;规定用例的执行的顺序&#xff0c;而且TestSuite也可以嵌套Tes…

AI芯片架构体系综述:芯片类型CPU\GPU\FPGA\ASIC以及指令集CSIS\RISC介绍

大模型的发展意味着算力变的越发重要&#xff0c;因为大国间科技竞争的关系&#xff0c;国内AI从业方在未来的一段时间存在着算力不确定性的问题&#xff0c;与之而来的是许多新型算力替代方案的产生。如何从架构关系上很好的理解计算芯片的种类&#xff0c;并且从计算类型、生…

FastBond2阶段2——基于ESP32C3开发的简易IO调试设备

1. 项目介绍 之前买了许多国产单片机esp32c3一直在吃灰&#xff0c;没有发挥它的真实价值。非常感谢硬禾组织的Fastbond2活动&#xff0c;刚好两者经过微妙的碰撞。恰可以用于FastBond2活动主题4 - 测量仪器&#xff08;单片机开发测试领域&#xff09;&#xff0c;或者用于国…

虚拟机项目部署与发布

目录 一.单机项目 1.1. 本机测试 1.2.部署 二.前后端 3.1.准备 3.2.部署 今天就到这里了哦&#xff0c;希望能帮到你哦&#xff01;&#xff01;&#xff01; 一.单机项目 当我们拿到已开发完的项目后&#xff0c;首先需要在我们自己的主机上进行测试&#xff0c;开发完的…

【Shell脚本4】Shell 传递参数

Shell 传递参数 我们可以在执行 Shell 脚本时&#xff0c;向脚本传递参数&#xff0c;脚本内获取参数的格式为&#xff1a;$n。n 代表一个数字&#xff0c;1 为执行脚本的第一个参数&#xff0c;2 为执行脚本的第二个参数&#xff0c;以此类推…… 以下实例我们向脚本传递三个…

持续集成交付CICD:安装Jenkins Slave(从节点)

目录 一、实验 1.安装Jenkins Slave 二、问题 1.salve节点启动jenkins报错 2.终止命令行jenkins节点状态丢失 一、实验 1.安装Jenkins Slave&#xff08;从节点&#xff09; &#xff08;1&#xff09;查看jenkins版本 Version 2.414.2 (2) 添加节点 系统设置&#xf…

c++ 信息学奥赛 2047:【例5.16】过滤空格

#include<cstdio> using namespace std; char st[200]; int main() { while (scanf("%s",&st)1)printf("%s ",st); //%s 后要有一个空格&#xff0c;不能省略return 0; } 解析&#xff1a;本题中使用一个技巧&#xff0c;那就是scanf函数…