【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)

在这里插入图片描述

➡️博客首页       https://blog.csdn.net/Java_Yangxiaoyuan


       欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。


       本文章CSDN首发,欢迎转载,要注明出处哦!


       先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复!


上一篇博文:【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(一)


深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)

  • 一、✅线程的实现方式
    • 1.1✅使用内核线程实现
    • 1.2✅使用用户线程实现
    • 1.3✅使用用户线程加轻量级进程混合实现
  • 一、✅拓展知识仓
    • 2.1✅内核线程有什么优点和缺点
    • 2.2✅内核线程和用户线程的区别
    • 2.3✅内核线程有哪些应用场景
    • 2.4✅Java的线程实现
    • 2.5✅虚拟线程
    • 2.6 ✅虚拟线程和平台线程的区别
    • 2.7✅如何使用
    • 2.8 ✅性能差异


一、✅线程的实现方式


我们都知道,在操作系统中,线程是比进程更轻量级的调度执行单位,线程的引入可以把一个进程的盗源分配和执行调度分开,各个线程既可以共享进程资源,又可以独立调度。


其实,线程的实现方式主要有三种: 分别是使用内核线程实现使用用户线程实现以及使用用户线程加轻量级进程混合实现


1.1✅使用内核线程实现


内核线程(Kernel-Level Thread,KLT) 就是直接由操作系统内核 (Kernel) 支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler) 对线程进行调度,并负责将线程的任务映射到各个处理器上,并向应用程序提供API接口来管理线程


应用程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口—— 轻量级进程 (LightWeight Process,LWP),轻量级进程就是我们通常意义上所进的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。


有了内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作。


但是轻量级进程具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态 (User Mode)和内核态(Kernel Mode)中来回切换。 其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。


看一个简单的栗子:


  1. Java代码

/**
* 如何使用内核线程来创建一个简单的多线程程序
*/

public class KernelThreadExample {
    public static void main(String[] args) {
        KernelThread kernelThread = new KernelThread();
        kernelThread.start();
    }
}

  1. JNI头文件 (KernelThread.h):

#include <jni.h>
#include <pthread.h> // 用于创建线程
#include <stdio.h>   // 用于输出信息

// 声明本地方法
JNIEXPORT void JNICALL Java_KernelThreadExample_startNativeThread(JNIEnv *env, jobject obj);

  1. C/C++实现 (KernelThread.c):

#include "KernelThread.h"
#include <pthread.h> // 用于创建线程
#include <stdio.h>   // 用于输出信息

// 本地线程函数
void* threadFunction(void* arg) {
    printf("This is running in a native thread!\n");
    return NULL;
}

JNIEXPORT void JNICALL Java_KernelThreadExample_startNativeThread(JNIEnv *env, jobject obj) {
    pthread_t threadId;
    pthread_create(&threadId, NULL, threadFunction, NULL); // 创建线程
    pthread_detach(threadId); // 分离线程,这样主程序结束时线程也会结束
}

  1. 编译和链接:使用gcc或g++编译C/C++文件,并链接到Java的本地库。确保指定JNI头文件。例如:

gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libkernelThreadImpl.so KernelThread.c

  1. 运行Java程序:运行你的Java程序,确保已将生成的本地库(例如,libkernelThreadImpl.so)放在系统的库路径中或指定到java.library.path系统属性。例如:

java -Djava.library.path=. KernelThreadExample

  1. 观察输出:会看到“This is running in a native thread!”的消息输出,这表明你的代码正在新的内核线程中执行。

1.2✅使用用户线程实现


在用户空间建立线程库,通过运行时系统(Run-time System)来完成线程的管理,因为这种线程的实现是在用户空间的,所以操作系统的内核并不知道线程的存在,所以内核管理的还是进程,所以这种线程的切换不需要内核操作。


这种实现方式下,进程和线程之间的关系是一对多的。


这种线程实现方式的优点是线程切换快,并且可以运行在任何操作系统之上,只需要实现线程库就行了。但是缺点也比较明显,就是所有线程的操作都需要用户程序自己处理,并且因为大多数系统调用都是阻塞的,所以一旦一个进程阻塞了,那么进程中的所有线程也会被阻塞。还有就是多处理器系统中如何将线程映射到其他处理器上也是一个比较大的问题。


1.3✅使用用户线程加轻量级进程混合实现


还有一种混合实现的方式,就是线程的创建在用户空间完成,通过线程库进行,但是线程的调度是由内核来完成的。多个用户线程通过多路复用来复用多个内核线程。这个就不展开讲了。


一、✅拓展知识仓


2.1✅内核线程有什么优点和缺点


内核线程的优点主要包括:


  1. 高并发性:在多处理器系统上,内核能够同时调度同一进程中的多个线程执行,从而实现高并发性。
  2. 资源利用率高:当一个线程被阻塞时,内核可以调度同一进程中的其他线程继续执行,从而充分利用系统资源。
  3. 避免用户态切换开销:内核线程在内核态运行,避免了用户态和内核态之间的切换开销。

内核线程缺点:


  1. 系统开销大:相对于用户线程,内核线程需要更多的系统资源,如内核栈和上下文切换时的资源。
  2. 不适合小规模并发:对于小规模的并发,内核线程可能不是最优的选择,因为其创建和销毁成本较高。
  3. 不适合低延迟场景:内核线程的上下文切换时间较长,因此不适合需要低延迟的场景。
  4. 受限于操作系统调度策略:内核线程的行为受限于操作系统的调度策略,这可能影响其性能和行为。

内核线程适用于需要高并发和资源利用率的应用,如服务器和大数据处理等场景。但在小规模并发、低延迟或特定性能要求的场景中,用户线程或其他并发模型可能更合适。


2.2✅内核线程和用户线程的区别


内核线程和用户线程的区别主要体现在以下几个方面:


  1. 创建和销毁:内核线程由操作系统内核创建和销毁,而用户线程由应用程序创建和销毁。
  2. 运行模式:内核线程运行在内核态,可以访问操作系统的所有资源,而用户线程运行在用户态,只能访问应用程序的资源。
  3. 任务类型:内核线程可以执行任何操作系统提供的服务,如文件系统、网络等,而用户线程只能执行应用程序提供的服务。
  4. 调度和切换:内核线程的创建和销毁需要操作系统内核的支持,而用户线程的创建和销毁由应用程序自己控制。内核线程的切换需要操作系统内核的支持,而用户线程的切换由应用程序自己控制。内核线程的调度由操作系统内核负责,而用户线程的调度由应用程序自己控制。
  5. 权限:内核线程始终具有最高权限,且权限不低于用户线程。用户线程的权限可以调整,但无法超过内核线程的权限。
  6. 效率:对于用户线程来说,如果系统调用是阻塞的,那么整个进程都会阻塞。这时需要内核线程来处理,因为内核线程是在操作系统中实现的机制,它在操作系统中有线程控制块(TCB),在发生错误或运行权限到期需要进行上下文切换时,会主动向内核发送中断信号并停止执行,将运行权限交给内核,由内核的interrupt handler进行相应的处理。

内核线程和用户线程在多个方面存在显著差异,具体选择使用哪种方式要根据具体的应用场景和需求来决定。


2.3✅内核线程有哪些应用场景


内核线程的应用场景主要包括


  1. 服务器端编程:在服务器端编程中,内核线程常被用于处理大量并发的请求。由于内核线程可以同时处理多个请求,因此可以大大提高服务器的处理能力和吞吐量。

  1. 实时系统:在实时系统中,内核线程用于确保关键任务能够及时执行。通过使用内核线程,实时系统可以实现确定性的行为和快速的系统响应。

  1. 设备驱动程序:许多设备驱动程序使用内核线程来处理硬件事件或执行后台任务。这样可以将设备的操作与系统的其他部分隔离,并确保设备的正确和高效运行。

  1. 文件系统和网络协议:文件系统和网络协议通常使用内核线程来处理文件和网络操作。这样可以提高系统的整体性能,并确保这些操作的可靠性和高效性。

  2. 系统守护进程和服务:许多系统守护进程和服务使用内核线程来执行长期运行的后台任务。例如,syslog服务使用内核线程来读取并处理系统日志消息。


  1. 并行计算:在需要大规模并行处理的场景下,如高性能计算(HPC),内核线程可以用于实现高效的并行计算。通过将计算任务分解为多个子任务,并由多个内核线程同时执行,可以提高整体计算性能。

总之吧,内核线程在许多场景中都有广泛的应用,特别是在需要高并发、实时性、可靠性和高效的系统场景中


2.4✅Java的线程实现


开头讲的是操作系统的线程的实现的三种方式,不同的操作系统在实现线程的时候会采用不同的机制比如windows采用的是内核线程实现的,而Solaris则是通过混合模式实现的。


而Java作为一门跨平台的编程语言,实际上他的线程的实现其实是依赖具体的操作系统的。而比较常用的windows和linux来说,都是采用的内核线程的方式实现的。


也就是说,当我们在JAVA代码中创建一个Thread的时候,其实是需要映射到提作系统的线程的具体实现的,因为常见的通过内核线程实现的方式在创建、调度时都需要进行内核参与,所以成本比较高,尽管JAVA中提供了线程池的方式来避免重复创建线程,但是依旧有很大的优化空间。而且这种实现方式意味着受机器资源的影响,平台线程数也是有限制的。


2.5✅虚拟线程


JDK 19引入的虚拟线程,是JD 实现的轻量级线程,他可以避免上下文切换带来的的额外耗费。他的实现原理其实是JDK不再是每一个线程都一对一的对应一个操作系统的线程了,而是会将多个虚拟线程映射到少量操作系统线程中,通过有效的调度来避免那些上下文切换。


在这里插入图片描述


而且,我们可以在应用程序中创建非常多的虚拟线程,而不依赖于平台线程的数量。这些虚拟线程是由JVM管理的,因此它们不会增加额外的上下文切换开销,因为它们作为普通Java对象存储在RAM中。


2.6 ✅虚拟线程和平台线程的区别


首先,虚拟线程总是守护线程。setDaemon (false)方法不能将虚拟线程更改为非守护线程。所以,需要注意的是,当所有启动的非守护线程都终止时,JVM将终止。这意味着JM不会等待虚拟线程完成后才退出


其次,即使使用setPriority()方法,虚拟线程始终具有 normal 的优先级,且不能更改优先级。在虚拟线程上调用此方法没有效果。


还有就是,虚拟线程是不支持stop()、suspend()或resume()等方法。这些方法在虚拟线程上调用时会抛出UnsupportedOperationException异常。


2.7✅如何使用


接下来个绍一下,在JDK 19中如何使用虚拟线程。


首先,通过Thread.startVirtualThread0可以运行一个虚拟线程:


Thread.startVirtualThread(() -> {
	System.out.println("虚拟线程执行中...");
});

其次,通过 Thread.Builder 也可以创建虚拟线程,Thread类提供了ofPlatform()来创建一人平台线程ofVirtual0来创建虚拟线程。


Thread.Builder platformBuilder = Thread.ofplatform().name("平台线程");
Thread.Builder virtualBuilder = Thread.ofVirtual().name("虚拟线程");

Thread t1 = platformBuilder .start(() -> {...});
Thread t2 = virtualBuilder.start(() -> {...});

另外,线程池也支持了虚拟线程,可以通过 Executors.newVirtua ThreadPerlaskExecutor() 来创建康拟线程:


try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
	Intstream.range(0, 10000).forEach(i -> {
		executor.submit(() -> {
			Thread.sleep(Duration.ofseconds(1));
			return i:
		}};
	});
}

但是,其实并不建议虚拟线程和线程池一起使用,因为Java线程池的设计是为了避免创建新的操作系统线程的开销,但是创建虚拟线程的开销并不大,所以其实没必要放到线程池中。


2.8 ✅性能差异


我在这里说了大半天,虚拟线程到底能不能提升性能,能提升多少呢? 我们来做个测试。


我们写一个简单的任务,在控制台中打印消息之前等待1秒:


final AtomicInteger atomicInteger = new AtomicInteger();

Runnable runnable = () -> {
	try {
		Thread.sleep(Duration.ofseconds(1));
	} catch(Exception e) {
		System.out .println(e);
	}
	System.out.println("Work Done  - " + atomicInteger.incrementAndGet());
});

现在,我们将从这个Runnable创建10,000个线程,并使用虚拟线程和平台线程执行它们,以比较两者的性能。


先来我们比较熟悉的平台线程的实现:


Instant start = Instant.now();

try (var executor = Executors .newFixedThreadPool(100))  {
	for(int i = ; i < 10_000; i++) {
		executor.submit(runnable);
	}
}
Instant finish = Instant .now();
long timeElapsed = Duration.between(start, finish).toMillis();
System.out.printIn("总耗时 :" + timeElapsed);

输出结果为:


总耗时 : 102323


总耗时大概100秒左右。接下来再用虚拟线程跑一下看看。


在JDK 21中已经是正式功能了,但是在JDK 19中,虚拟线程是一个预览API,默认是禁用。所以需要使用$java–source 19–enable-preview xxjava 的方式来运行代码。


Instant start = Instant.now();

try  (var executor = Executors.newVirtualThreadPerTaskExecutor())  {
	for(int i = ; i < 10_000; i++) {
		executor.submit(runnable);
	}
}

Instant finish = Instant.now();

long timeElapsed = Duration.between(start, finish).toMillis():

System.out.printIn("总耗时 :" + timeElapsed);

使用 Executors.newVirtualThreadPerTaskExecutor来创建虚拟线程,执行结果如下:


总耗时:1674


总耗时大概1.6秒左右。


100秒和1.6秒的差距,足以看出虚拟线程的性能提升还是立竿见影的。


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

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

相关文章

Arthas项目实战用法

一、简介 1、项目所在位置 2、安装Arthas 1. 下载arthas-boot.jar 2. 运行arthas-boot.jar 3. 选择进程 3、卸载Arthas 二、核心监视功能 1、monitor&#xff1a;监控方法的执行情况 2、watch&#xff1a;检测函数返回值 3、trace&#xff1a;根据路径追踪&#xff0c;…

低代码自动化测试的实践

何为低代码测试 传统上&#xff0c;功能、 UI、端到端等测试自动化的实现都涉及编写测试脚本&#xff0c;代替测试人员执行重复的手动测试任务。自动化脚本的开发工作通常由 QA 工程师或开发人员完成&#xff0c;这需要编写大量代码。 而低代码甚至无代码的理念也是在自动化测…

海外盲盒系统搭建:盲盒出海成为了盲盒企业的新机遇

盲盒在我国作为一个新型的消费模式&#xff0c;取得了较大的成功&#xff0c;也成为了当下流行的潮玩文化。盲盒在国内爆火的优势下&#xff0c;也为我国盲盒出海提供了市场基础。当下&#xff0c;海外市场具有较大的发展潜力&#xff0c;除此之外&#xff0c;盲盒出海也成为当…

transformer详解

transformer 框架注意力机制注意力评分函数(scoring function)自注意力机制&#xff08;self-attention&#xff09;masked self-attention多头注意力&#xff08;multi-head attention&#xff09; 位置编码Layer normalizationResnet&#xff08;Add&#xff09; 框架 框架解…

使用AI自动生成PPT提高制作效率

使用AI自动生成PPT提高制作效率 在制作PPT方面&#xff0c;很多制作者都会轻易跳进一个怪圈&#xff1a;“我要制作一个关于关爱老人的PPT&#xff0c;该怎么做呢&#xff0c;有模板没有?”这个会涉及很多逻辑需要经过不断的思考&#xff0c;制作PPT要通过很多素材、使用技巧、…

【报错】检索 COM 类工厂中 CLSID 为 {28E68F9A-8D75-11D1-8DC3-3C302A000000} 的组件失败错误

情况描述 在使用C#进行工控软件开发&#xff0c;需要连接通过OPC连接DCS系统时&#xff0c;需要通过visual studio的NuGet包管理器添加OpcAutomation&#xff0c;如下图所示&#xff1a; 这样便可以实现通过C#连接读取数据。但是在通过C#连接OPC server时&#xff0c;会出现错…

性能压力测试:企业成功的关键要素

性能压力测试在现代企业软件开发中扮演着至关重要的角色&#xff0c;它不仅仅是一项技术手段&#xff0c;更是保障企业成功的关键要素。本文将探讨性能压力测试在企业中的重要性&#xff0c;并阐述其对业务稳定性、用户体验和品牌声誉的影响。 一、保障业务稳定性 1、应对高负载…

java数据结构与算法刷题-----LeetCode96. 不同的二叉搜索树

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 很多人觉得动态规划很难&#xff0c;但它就是固定套路而已。其实动态规划只…

走迷宫之推箱子

前言&#xff1a; 在上一篇文章当中我介绍了一个走迷宫的写法&#xff0c;但是那个迷宫没什么可玩性和趣味性&#xff0c;所以我打算在迷宫的基础上加上一个推箱子&#xff0c;使之有更好的操作空间&#xff0c;从而增强了游戏的可玩性和趣味性。 1. 打印菜单 void menu() {…

[WUSTCTF2020]alison_likes_jojo 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 得到的 flag 请包上 flag{} 提交。 感谢 Iven Huang 师傅供题。 比赛平台&#xff1a;https://ctfgame.w-ais.cn/ 密文&#xff1a; 下载附件解压&#xff0c;得到两张jpg图片和一个文本文件。 解题思路&#x…

HNU-编译原理-实验2-Bison

编译原理实验2Bison 计科210X 甘晴void 202108010XXX 实验要求 详细的实验项目文档为 https://gitee.com/coderwym/cminus_compiler-2023-fall/tree/master/Documentations/lab2 实验步骤 本次实验需要在 Lab1 已完成的 flex 词法分析器的基础上&#xff0c;进一步使用 b…

【Java】源码文件开头添加注释

需求 应公司质量部要求&#xff0c;需要对代码做静态检查。质量部要求&#xff0c;源码文件必须在起始行起设置一些注释&#xff0c;然而项目已经开发了一年之久&#xff0c;且没有维护这个注释。 此时&#xff0c;面对好几千个源码文件&#xff0c;我们如何快速添加相应的注…

滑块拖动验证

效果 代码 svg图标 初始图标 <svg t"1705397535958" class"icon" viewBox"0 0 1024 1024" version"1.1" xmlns"http://www.w3.org/2000/svg" p-id"1492" width"200" height"200">&l…

Kubernetes核心实战

kubernetes&#xff08;一&#xff09;概述与架构 kubernetes&#xff08;二&#xff09;创建集群 云原生实战 语雀 官网 Kubernetes 文档 | Kubernetes B站课程&#xff1a;https://www.bilibili.com/video/BV13Q4y1C7hS/?p41 1.Namespace 名称空间&#xff0c;用来对集群…

【书生·浦语】大模型实战营——LMDeploy 大模型量化部署实战

大模型部署背景 大模型部署是指将训练好的模型在特定的软硬件环境中启动的过程&#xff0c;使模型能够接收输入并返回预测结果。大模型的内存开销巨大&#xff0c;7B模型仅权重需要14G内存。另外大模型是自回归生成&#xff0c;需要缓存Attention的 k/v。 LMDeploy 简介 推理性…

SpringMVC请求源码分析

文章目录 一、SpringMVC简介1. 概念2. 从Servlet到SprigMVC3. SpringMVC的XML实现4. SpringMVC的请求流程 二、SpringMVC源码分析1. SpringMVC启动流程验证2. 细节补充 一、SpringMVC简介 1. 概念 官网介绍 Spring Web MVC is the original web framework built on the Servl…

【Leetcode】82. 删除排序链表中的重复元素 II

文章目录 题目思路代码 题目 82. 删除排序链表中的重复元素 II 题目&#xff1a;给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,…

uni-app的项目创建和环境搭建

uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、Web&#xff08;响应式&#xff09;、以及各种小程序&#xff08;微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝&#xff09;、快应用等多个平台。 第一步…

Linux的权限(1)

目录 操作系统的"外壳"程序 外壳程序是什么&#xff1f; 为什么存在外壳程序&#xff1f; 外壳程序怎么运行操作&#xff1f; 权限 什么是权限&#xff1f; 权限的本质&#xff1f; Linux中的&#xff08;人&#xff09;用户权限&#xff1f; su和su -的区别…

C for Graphic:Sliced Circle Image

不做UI不知道&#xff0c;没想到时至今日&#xff0c;ugui居然没有sliced filled image模式&#xff0c;用circle做filled&#xff0c;不能用sliced九宫格图&#xff0c;导致每次使用这个效果必须一张新图&#xff0c;何其浪费资源。 原始功能如下&#xff1a; 我…