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

news2024/11/20 2:47:36

在这里插入图片描述

➡️博客首页       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秒的差距,足以看出虚拟线程的性能提升还是立竿见影的。


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

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

相关文章

Linux------进程的fork()详解

目录 前言 一、fork()的使用 二、fork()的返回值 我们为什么要创建子进程&#xff1f; 父进程与子进程的分流 三、fork的一些难理解的问题 1.fork干了什么事情&#xff1f; 2.fork为什么会有两个返回值 3.fork的两个返回值&#xff0c;为什么会给父进程返回子进程pid…

Linux Mii management/mdio子系统分析之六 fixed-mii_bus分析(mac2mac分析)

&#xff08;转载&#xff09;原文链接&#xff1a;[https://blog.csdn.net/u014044624/article/details/130674908] (https://blog.csdn.net/u014044624/article/details/130674908) 前面几章我们介绍了MDIO模块的大部分内容&#xff0c;针对mii_bus、mdio_bus、phy_device、p…

鸿蒙开发的前景趋势及薪资水平展望

随着科技的迅猛发展和数字化转型的推进&#xff0c;鸿蒙系统作为国内领先的分布式操作系统&#xff0c;已经在市场中崭露头角&#xff0c;展现出独特的技术优势和广阔的应用前景。对于开发者而言&#xff0c;掌握鸿蒙开发技能不仅意味着拥有更多的职业发展机会&#xff0c;还预…

Win10输入密码不满足密码策略要求的解决方法

在Win10电脑中用户输入密码的时候&#xff0c;收到了不满足密码策略要求的提示&#xff0c;导致用户不能成功设置密码。用户先打开Win10系统的组策略编辑器&#xff0c;点击关闭密码必须符合复杂性要求功能保存即可。以下小编将分享Win10密码不符合策略要求的解决方法步骤&…

Relation-Aware Graph Transformer for SQL-to-Text Generation

Relation-Aware Graph Transformer for SQL-to-Text Generation Abstract SQL2Text 是一项将 SQL 查询映射到相应的自然语言问题的任务。之前的工作将 SQL 表示为稀疏图&#xff0c;并利用 graph-to-sequence 模型来生成问题&#xff0c;其中每个节点只能与 k 跳节点通信。由…

Linux -- firewalld的富语言规则

1. Firewalld支持两种类型的NAT&#xff1a;IP地址伪装和端口转发。 &#xff08;1&#xff09;IP地址伪装 地址伪装&#xff08;masquerade)&#xff1a;通过地址伪装&#xff0c;NAT 设备将经过设备的包转发到指定接收方&#xff0c;同时将通过的数据包的源地址更改为其自己的…

WBTT:“Fair Launch”如何做到更加公平

铭文是一种全新的资产发行方案&#xff0c;它让非图灵完备的链上生态具备发行资产的能力&#xff0c;而铭文赛道的兴起也让比特币生态再次回到加密世界的中心。铭文市场的兴起&#xff0c;更被称之为“散户的狂欢”&#xff0c;因为这种“Fair Launch”的启动方式正在让所有参与…

Webpack 怎么实现按需异步加载模块

要弄懂这个问题&#xff0c;需要先来看关于webpack打包的3个问题。 三个问题 第一个问题 项目中的json文件&#xff0c;如何使用webpack进行处理&#xff1f; 如果我们希望把json文件当做静态配置&#xff0c;例如有如下json文件 {"version": "1.0.0"…

[Android]实现一个权限申请类

[Android]实现一个权限申请类 导言 在引入了动态权限申请之后&#xff0c;Android的权限申请就变得尤为繁琐&#xff0c;若是按照原有的方法一板一眼地进行申请&#xff0c;样板代码未免太多。因此本篇文章就使用ActivityResult API&#xff0c;来实现一个简单的权限申请类来帮…

国标视频监控平台EasyCVR如何通过接口调用下载设备录像文件

安防监控系统国标GB28181协议EasyCVR视频监控平台采用了开放式的网络结构&#xff0c;平台可支持Windows/Linux(CentOS ubuntu)/国产麒麟系统&#xff0c;能在局域网、公网、专网等复杂的网络环境中&#xff0c;将场景中分散的海量网络监控设备进行统一接入与汇聚管理&#xff…

USB Cable导致连接识别不良

2根USB线&#xff0c;连接USB2RS232芯片&#xff0c;有根线能够识别&#xff0c;另外一根不能识别。 好的线识别如下&#xff1a; 另外一根就不能识别

GZ036 区块链技术应用赛项赛题第1套

2023年全国职业院校技能大赛 高职组 “区块链技术应用” 赛项赛卷(1卷) 任 务 书 参赛队编号: 背景描述 随着消费需求的不断变化,消费者对食品安全的关注度越来越高,希望能参与食品供应链管理,让每个环节都透明化。但传统的供应链管理依靠纸张记录,保存数…

[自动驾驶算法][从0开始轨迹预测]:二、自动驾驶系统中常用的坐标系及相应的转换关系

自动驾驶中常见的坐标系与坐标转换 1. 传感器坐标系1.1 相机坐标系统1) 相机相关基础知识2) 相机各坐标系图像/像素坐标系相机坐标系像平面坐标系 3) 相机各坐标系之间的转换像平面坐标系到像素坐标系的转换&#xff08;平移缩放变换&#xff09;相机坐标系转像平面坐标系&…

Oracle基础查询介绍

1、oracle语句分为&#xff1a; DCL&#xff1a;数据控制语言&#xff0c;关键字有 grant、revoke 如&#xff1a;grant create table to test2; DDL&#xff1a;数据定义语言&#xff0c;关键字有 create、alter、drop、truncate 如&#xff1a;create table test1; DML&am…

接口测试用例设计 - 实战篇

一&#xff0e;接口测试流程 1&#xff0e;需求讨论 2&#xff0e;需求评审 3&#xff0e;场景设计 4&#xff0e;数据准备 5&#xff0e;执行 二&#xff0e;分析接口文档中哪些元素 1&#xff0e;接口名称 2&#xff0e;接口地址 3&#xff0e;支持格式 4&#xff0…

IDEA2023的激活与安装(全网最靠谱,最快捷的方式)

前言&#xff1a; 相信很多小伙伴已经开始了java的学习之旅&#xff0c;想要更快乐的学习当然少不了IDEA这个得力的开发工具软件。但是IDEA是付费的&#xff0c;免费版功能有太少&#xff0c;怎么才能既免费&#xff0c;又能使用上正式版呢&#xff01;当然还是激活啦&#xf…

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

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…

Leetcode22-旅行终点站(1436)

1、题目 给你一份旅游线路图&#xff0c;该线路图中的旅行线路用数组 paths 表示&#xff0c;其中 paths[i] [cityAi, cityBi] 表示该线路将会从 cityAi 直接前往 cityBi 。请你找出这次旅行的终点站&#xff0c;即没有任何可以通往其他城市的线路的城市。 题目数据保证线路…

【Macos系统】安装VOSviewer及使用VOSviewer教程!!以ESN网络的研究进行案例分析

【Macos系统】安装VOSviewer及使用VOSviewer教程 以ESN网络的研究进行案例分析 本文介绍如何安装和使用VOSviewer软件&#xff0c;并以ESN&#xff08;Echo State Network&#xff09;网络的研究为案例进行分析。利用VOSviewer对相关文献进行可视化分析&#xff0c;并深入了解…

最新可用GPT-3.5、GPT-4、Midjourney绘画、DALL-E3文生图模型教程【宝藏级收藏】

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;文档对话总结DALL-E3文生图&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和…