[Java基础]虚拟线程

news/2024/9/18 9:01:16/文章来源:https://www.cnblogs.com/DCFV/p/18378235

虚拟线程(Virtual Thread)是 JDK 而不是 OS 实现的轻量级线程(Lightweight Process,LWP),由 JVM 调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。

虚拟线程和平台线程有什么关系?
在引入虚拟线程之前,java.lang.Thread 包已经支持所谓的平台线程(Platform Thread),也就是没有虚拟线程之前,我们一直使用的线程。JVM 调度程序通过平台线程(载体线程)来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程(多个虚拟线程挂载在一个平台线程上),当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程。

虚拟线程、平台线程和系统内核线程的关系图如下所示(图源:How to Use Java 19 Virtual Threads):

image

关于平台线程和系统内核线程的对应关系多提一点:在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个平台线程对应一个系统内核线程。Solaris 系统是一个特例,HotSpot VM 在 Solaris 上支持多对多和一对一。具体可以参考 R 大的回答: JVM 中的线程模型是用户级的么?。

虚拟线程有什么优点和缺点?
优点
非常轻量级:可以在单个线程中创建成百上千个虚拟线程而不会导致过多的线程创建和上下文切换。
简化异步编程: 虚拟线程可以简化异步编程,使代码更易于理解和维护。它可以将异步代码编写得更像同步代码,避免了回调地狱(Callback Hell)。
减少资源开销: 由于虚拟线程是由 JVM 实现的,它能够更高效地利用底层资源,例如 CPU 和内存。虚拟线程的上下文切换比平台线程更轻量,因此能够更好地支持高并发场景。
缺点
不适用于计算密集型任务: 虚拟线程适用于 I/O 密集型任务,但不适用于计算密集型任务,因为密集型计算始终需要 CPU 资源作为支持。
与某些第三方库不兼容: 虽然虚拟线程设计时考虑了与现有代码的兼容性,但某些依赖平台线程特性的第三方库可能不完全兼容虚拟线程。
如何创建虚拟线程?
官方提供了以下四种方式创建虚拟线程:

使用 Thread.startVirtualThread() 创建
使用 Thread.ofVirtual() 创建
使用 ThreadFactory 创建
使用 Executors.newVirtualThreadPerTaskExecutor()创建
1、使用 Thread.startVirtualThread() 创建

public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
Thread.startVirtualThread(customThread);
}
}

static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
2、使用 Thread.ofVirtual() 创建

public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
// 创建不启动
Thread unStarted = Thread.ofVirtual().unstarted(customThread);
unStarted.start();
// 创建直接启动
Thread.ofVirtual().start(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
3、使用 ThreadFactory 创建

public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(customThread);
thread.start();
}
}

static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
4、使用Executors.newVirtualThreadPerTaskExecutor()创建

public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
虚拟线程和平台线程性能对比
通过多线程和虚拟线程的方式处理相同的任务,对比创建的系统线程数和处理耗时。

说明:统计创建的系统线程中部分为后台线程(比如 GC 线程),两种场景下都一样,所以并不影响对比。

测试代码:

public class VirtualThreadTest {
static List list = new ArrayList<>();
public static void main(String[] args) {
// 开启线程 统计平台线程数
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() -> {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
updateMaxThreadNum(threadInfo.length);
}, 10, 10, TimeUnit.MILLISECONDS);

    long start = System.currentTimeMillis();// 虚拟线程ExecutorService executor =  Executors.newVirtualThreadPerTaskExecutor();// 使用平台线程// ExecutorService executor =  Executors.newFixedThreadPool(200);for (int i = 0; i < 10000; i++) {executor.submit(() -> {try {// 线程睡眠 0.5 s,模拟业务处理TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException ignored) {}});}executor.close();System.out.println("max:" + list.get(0) + " platform thread/os thread");System.out.printf("totalMillis:%dms\n", System.currentTimeMillis() - start);}
// 更新创建的平台最大线程数
private static void updateMaxThreadNum(int num) {if (list.isEmpty()) {list.add(num);} else {Integer integer = list.get(0);if (num > integer) {list.add(0, num);}}
}

}
请求数 10000 单请求耗时 1s:

// Virtual Thread
max:22 platform thread/os thread
totalMillis:1806ms

// Platform Thread 线程数200
max:209 platform thread/os thread
totalMillis:50578ms

// Platform Thread 线程数500
max:509 platform thread/os thread
totalMillis:20254ms

// Platform Thread 线程数1000
max:1009 platform thread/os thread
totalMillis:10214ms

// Platform Thread 线程数2000
max:2009 platform thread/os thread
totalMillis:5358ms
请求数 10000 单请求耗时 0.5s:

// Virtual Thread
max:22 platform thread/os thread
totalMillis:1316ms

// Platform Thread 线程数200
max:209 platform thread/os thread
totalMillis:25619ms

// Platform Thread 线程数500
max:509 platform thread/os thread
totalMillis:10277ms

// Platform Thread 线程数1000
max:1009 platform thread/os thread
totalMillis:5197ms

// Platform Thread 线程数2000
max:2009 platform thread/os thread
totalMillis:2865ms
可以看到在密集 IO 的场景下,需要创建大量的平台线程异步处理才能达到虚拟线程的处理速度。
因此,在密集 IO 的场景,虚拟线程可以大幅提高线程的执行效率,减少线程资源的创建以及上下文切换。
注意:有段时间 JDK 一直致力于 Reactor 响应式编程来提高 Java 性能,但响应式编程难以理解、调试、使用,最终又回到了同步编程,最终虚拟线程诞生。

虚拟线程的底层原理是什么?

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

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

相关文章

一台电脑配置两个Git账号(github和gitlab),不同仓库使用不同的git

我们工作时一般都是使用gitlab,工作电脑也一般配置的 git 是连接 gitlab 的,那么当我们如果想用同一个电脑实现不同仓库根据自己的需要到底是推送代码到github还是 gitlab,以及使用哪个账号,(比如如果想用工作电脑维护自己的 github 项目,但是怕后面被公司追究用工作电脑干…

DDD是软件工程的第一性原理?

本文书接上回《DDD建模后写代码的正确姿势》,关注公众号(老肖想当外语大佬)获取信息:最新文章更新;DDD框架源码(.NET、Java双平台);加群畅聊,建模分析、技术实现交流;视频和直播在B站。前提 本文需要以系列前文的逻辑链条和结论为前提,如果没有阅读过前文的,可以阅…

[TCP/IP]三次握手

三次握手TCP建立连接为什么需要三次握手 TCP作为一种可靠传输控制协议,其核心思想:既要保证数据可靠传输,又要提高传输的效率,而用三次恰恰可以满足以上两方面的需求! TCP可靠传输的精髓:TCP连接的一方A,由操作系统动态随机选取一个32位长的序列号(Initial Sequence Nu…

预设型 DP

预设型 DP《美好的一天》--青春学概论 한 잔 술에 취해 잠긴 목엔 沉醉于一杯酒 갈라지는 목소린 다시 带着沙哑的嗓音 두 잔 자기 전엔 기분 좋음 入睡前饮下第二杯让心情愉悦 알 수 없는 세상에 빠져 陷入不可预知的世界 세 잔 또 네 잔 술에 빠진 又沉醉于第三杯第四杯 세상…

Nginx缓存服务

Nginx缓存服务 7.1 缓存配置语法 proxy_cache 配置语法 S yntax: proxy_cache zone | off; Default: proxy_cache off; Context: http, server, location//缓存路径 Syntax: proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time…

第八周进度报告

常用API SimpleDateFormat格式化:把时间变成我们喜欢的格式 解析:把字符串表示的时间变成Date对象import java.text.SimpleDateFormat; import java.util.Date;public static void main(String[] args) throws ParseException {/*public SimpleDateFormat() //默认格式publi…

学习Java的第八周

随着24年暑假的结束,第八周的Java学习也结束了,本周主要学习了字符串,包括API简介(提供各种功能的可以直接使用的类),字符串的概述、构造实现(创建String对象的两种方式,它们的不同点)、内存分析(两种定义方式各自的内存原理)、字符串比较方法(equals和equalsIgnor…

XGBoost中正则化的9个超参数

正则化是一种强大的技术,通过防止过拟合来提高模型性能。本文将探索各种XGBoost中的正则化方法及其优势。为什么正则化在XGBoost中很重要? XGBoost是一种以其在各种机器学习任务中的效率和性能而闻名的强大算法。像任何其他复杂模型一样,它可能会过拟合,特别是在处理噪声数据或…

FPGA基础:格雷码及FIFO应用概述

1简介 反射二进制码反射二进制码(RBC),也称为反射二进制(RB)或格雷码(Gray code),得名于Frank Gray,是二进制数制的一种排列方式,使得连续两个值之间仅有一个比特(二进制位)不同。格雷码在数字电路中的应用有助于减少误差、提高系统的可靠性,并简化逻辑操作,因此…

[操作系统]访问一个逻辑地址发生了什么

当CPU想要访问一个逻辑地址的时候,我们需要做两个步骤,地址转换和内存访问 地址转换 逻辑地址是程序内部使用的地址,并非真正的物理地址。 从逻辑地址到物理地址的映射,由页表来完成,页表的内容包括,逻辑页号,物理页号,有效位,有效位表示这一页是否在内存中。 页表存放…

[Ynoi2011] 初始化

题目链接 : [Ynoi2011] 初始化 神仙trick + 卡常题,前缀后缀和根本没听过。 根号分治 + 分块。 对于修改操作,发现是跳着修改,考虑根号分治。若\(x \ge \sqrt{n}\),直接暴力更改,复杂度\(O(\sqrt{n})\)。 反之,可以将序列抽象成一堆大小为\(x\)的段,如图,\(l,r\)是查询…