JDK21虚拟线程下的代码变化

乐云一
  • Java
  • Java
About 1656 wordsAbout 6 min

JDK21虚拟线程下的代码变化

概述

在虚拟线程未诞生前,也就是如今 你发任你发,我用JAVA8 的版本;

我们选择的任何线程开发模式都是由JVM发起而向操作系统申请线程资源的一请求,一创建模式;

所以在编写并发代码时,为避免这种操作系统频繁创建资源出现性能损耗问题,无论是谁都推荐你采用线程池;使JVM提前装配可随意调配的线程资源,减少一请求,一创建的场景;

不过对于系统来说,这样的节省仅仅只是在有限的容器内申请资源,但是创建线程带来的问题依然存在。

在这里稍微过一下线程创建对系统来说的一些弊端:

  1. JDK创建线程的本质是向操作系统申请一个系统线程,而系统进程有限
  2. 系统分配线程,资源创建与销毁都会涉及到硬件级别的上下文切换,而平台应用与操作系统的IO交互就会出现用户态上下文切换导致的性能阻塞,即创建、销毁流程中阻塞IO,导致CPU在阻塞中出现无事可做的浪费情况
  3. 最后是由于JDK创建与系统线程1对1,意味着创建一个线程消耗10KB,系统运行内存就要被占用10KB;这在IO密集型应用中,非常的不友好,而我们大多数应用都需要对接数据库进行CRUD操作,以至于为IO创建频繁的类型
  4. ...

image-20241031112204610

总结来说,JDK的线程要存在系统内存中,因此由系统实际掌控,管理权给JVM,理论上线程自然也就有限且有损耗

实现与概念

以上,我们可知JVM线程在当今设计理念中并不完美,也因此在其他语言中例如go:使用 协程 这一概念用来补全解决上述弊端,而虚拟线程 则是JDK版的 协程

对比虚拟线程与平台线程的不同:

含义平台线程虚拟线程
平台操作系统OSJVM应用平台
实现一个平台线程向OS申请一个OS线程由JVM自行实现管理,仅在用户空间运行,在多个OS线程调度
成本设计到操作系统的资源分配,用户态上下文切换资源仅在JVM内部管控,因而资源创建、调度、销毁都不涉及系统内存
扩展受限与操作系统内存与进程数量几乎无限,仅受限与JVM虚拟机大小
场景CPU密集型,对线程性能有要求IO密集型

可直观见虚拟线程,在理念层面很像线程池的实现:

  1. 避免线程创建和销毁的开销

但是因为虚拟线程的线程创建、销魂和运行都是基于代码层面的管控,其损耗几乎可忽略不计;因此与线程池对比,虚拟线程几乎可以完全取代线程池的使用;

实现

image-20241101110918221

虽然名为虚拟,实际还是需要依靠平台线程执行任务;JVM维护了一个由ForkJoinPool创建的平台线程池,其数量默认等于CPU核心的数量,最大值不超过256.

当虚拟线程创建时,JVM会将其任务调度到其一的平台线程中,将虚拟线程中的堆栈内存直接复制到平台线程的堆栈中,进而该平台线程成为了此次虚拟线程执行任务的载体;

而当该虚拟线程被阻塞时,仅仅只是在虚拟线程队列中将其挂起,并不会影响平台线程被其他的虚拟线程当成载体线程使用;

用通俗的话来讲,虚拟线程像排队,但队列有很多个,且排队的人可以随意换队,看到哪个队伍少就可以排哪个,而这个队伍的终点就是“运行”

代码变化

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
   while(true) {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
	}
}

这就是虚拟线程从创建到执行的全部代码

可以看出JDK将虚拟线程中,挂起、等待、分配等等操作非常简单的封装成了普通的api方法;

但是对我们现有的开发逻辑来说,变化可以说是沧海桑田,当然仅仅只是针对需要用到虚拟线程的IO密集型业务

往日对于一条数据的插入:

private ThreadPoolExecutor testPool;

public void handler() {
    testPool.execute(() -> {
 		//TODO
        xxdao.save(do);
    });
}

我们需要考虑:

  1. 线程池配置
  2. 线程池下嵌套线程的隔离问题
  3. dao方法阻塞问题
  4. ....

且外,随着我们项目复杂性的增加,线程池与线程池之间的相互干扰问题也会几何量的增长,极大的危害一个项目的稳定扩展;

但是随着虚拟线程的引入,我们在进行上述业务开发的过程中,完全可不需要考虑到原线程池带来的影响因素,而非常简单的使用 newVirtualThreadPerTaskExecutor 就能保证任务的高效执行;

不过需要注意的是,以上理论只针对需要频繁访问IO或不追求性能的业务,由于虚拟线程是基于平台线程这个载体而平台线程并不服务于一个虚拟线程,这也就导致了原线程的使用,任务的执行性能是大于虚拟线程的;

总结

JDK21是在2023年9月19日发行的,在这个版本之前JDK的升级并没有引起各大产商的着重重视;不过自JDK21开始,由于是长期支持的版本,各大产商也逐渐重视这一版本的功能甚至重构。

一是因为LTS版本将被长期支持和维护,二则是因为引入Project loom概念,即协程支持,使得JAVA在性能上对比其他语言在并发编程上更具有竞争力了。加上JDK21完善的生态环境,可以说21版本很可能成为下一个JDK8。

Last update:
Contributors: LeYunone
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.14.7