网站建设开发 脚本语言,网站开发教程流程,通辽市做网站公司,公共资源中心网站建设➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你#x1f44d;点赞、#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发#xff0c;欢迎转载#xff0c;要注明出处哦#xff01; 先感谢优秀的你能认真的看完本文… ➡️博客首页 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)中来回切换。 其次每个轻量级进程都需要有一个内核线程的支持因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间)因此一个系统支持轻量级进程的数量是有限的。 看一个简单的栗子 Java代码 /**
* 如何使用内核线程来创建一个简单的多线程程序
*/public class KernelThreadExample {public static void main(String[] args) {KernelThread kernelThread new KernelThread();kernelThread.start();}
}JNI头文件 (KernelThread.h): #include jni.h
#include pthread.h // 用于创建线程
#include stdio.h // 用于输出信息// 声明本地方法
JNIEXPORT void JNICALL Java_KernelThreadExample_startNativeThread(JNIEnv *env, jobject obj);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); // 分离线程这样主程序结束时线程也会结束
}编译和链接使用gcc或g编译C/C文件并链接到Java的本地库。确保指定JNI头文件。例如 gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared -o libkernelThreadImpl.so KernelThread.c运行Java程序运行你的Java程序确保已将生成的本地库例如libkernelThreadImpl.so放在系统的库路径中或指定到java.library.path系统属性。例如 java -Djava.library.path. KernelThreadExample观察输出会看到“This is running in a native thread!”的消息输出这表明你的代码正在新的内核线程中执行。 1.2✅使用用户线程实现 在用户空间建立线程库通过运行时系统(Run-time System)来完成线程的管理因为这种线程的实现是在用户空间的所以操作系统的内核并不知道线程的存在所以内核管理的还是进程所以这种线程的切换不需要内核操作。 这种实现方式下进程和线程之间的关系是一对多的。 这种线程实现方式的优点是线程切换快并且可以运行在任何操作系统之上只需要实现线程库就行了。但是缺点也比较明显就是所有线程的操作都需要用户程序自己处理并且因为大多数系统调用都是阻塞的所以一旦一个进程阻塞了那么进程中的所有线程也会被阻塞。还有就是多处理器系统中如何将线程映射到其他处理器上也是一个比较大的问题。 1.3✅使用用户线程加轻量级进程混合实现 还有一种混合实现的方式就是线程的创建在用户空间完成通过线程库进行但是线程的调度是由内核来完成的。多个用户线程通过多路复用来复用多个内核线程。这个就不展开讲了。 一、✅拓展知识仓 2.1✅内核线程有什么优点和缺点 内核线程的优点主要包括 高并发性在多处理器系统上内核能够同时调度同一进程中的多个线程执行从而实现高并发性。资源利用率高当一个线程被阻塞时内核可以调度同一进程中的其他线程继续执行从而充分利用系统资源。避免用户态切换开销内核线程在内核态运行避免了用户态和内核态之间的切换开销。 内核线程缺点 系统开销大相对于用户线程内核线程需要更多的系统资源如内核栈和上下文切换时的资源。不适合小规模并发对于小规模的并发内核线程可能不是最优的选择因为其创建和销毁成本较高。不适合低延迟场景内核线程的上下文切换时间较长因此不适合需要低延迟的场景。受限于操作系统调度策略内核线程的行为受限于操作系统的调度策略这可能影响其性能和行为。 内核线程适用于需要高并发和资源利用率的应用如服务器和大数据处理等场景。但在小规模并发、低延迟或特定性能要求的场景中用户线程或其他并发模型可能更合适。 2.2✅内核线程和用户线程的区别 内核线程和用户线程的区别主要体现在以下几个方面 创建和销毁内核线程由操作系统内核创建和销毁而用户线程由应用程序创建和销毁。运行模式内核线程运行在内核态可以访问操作系统的所有资源而用户线程运行在用户态只能访问应用程序的资源。任务类型内核线程可以执行任何操作系统提供的服务如文件系统、网络等而用户线程只能执行应用程序提供的服务。调度和切换内核线程的创建和销毁需要操作系统内核的支持而用户线程的创建和销毁由应用程序自己控制。内核线程的切换需要操作系统内核的支持而用户线程的切换由应用程序自己控制。内核线程的调度由操作系统内核负责而用户线程的调度由应用程序自己控制。权限内核线程始终具有最高权限且权限不低于用户线程。用户线程的权限可以调整但无法超过内核线程的权限。效率对于用户线程来说如果系统调用是阻塞的那么整个进程都会阻塞。这时需要内核线程来处理因为内核线程是在操作系统中实现的机制它在操作系统中有线程控制块TCB在发生错误或运行权限到期需要进行上下文切换时会主动向内核发送中断信号并停止执行将运行权限交给内核由内核的interrupt handler进行相应的处理。 内核线程和用户线程在多个方面存在显著差异具体选择使用哪种方式要根据具体的应用场景和需求来决定。 2.3✅内核线程有哪些应用场景 内核线程的应用场景主要包括 服务器端编程在服务器端编程中内核线程常被用于处理大量并发的请求。由于内核线程可以同时处理多个请求因此可以大大提高服务器的处理能力和吞吐量。 实时系统在实时系统中内核线程用于确保关键任务能够及时执行。通过使用内核线程实时系统可以实现确定性的行为和快速的系统响应。 设备驱动程序许多设备驱动程序使用内核线程来处理硬件事件或执行后台任务。这样可以将设备的操作与系统的其他部分隔离并确保设备的正确和高效运行。 文件系统和网络协议文件系统和网络协议通常使用内核线程来处理文件和网络操作。这样可以提高系统的整体性能并确保这些操作的可靠性和高效性。 系统守护进程和服务许多系统守护进程和服务使用内核线程来执行长期运行的后台任务。例如syslog服务使用内核线程来读取并处理系统日志消息。 并行计算在需要大规模并行处理的场景下如高性能计算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秒的差距足以看出虚拟线程的性能提升还是立竿见影的。