JDK21中虚拟线程到底是什么?看完便知

2023年 9月 30日 113.2k 0

本文涉及到的技术:虚拟线程、结构化并发、线程池、TheadLocal,对原理感兴趣的可以直接跳到原理部分。

虚拟线程是JDK19中引入的,JDK21正式发布,我们先来看看虚拟线程的几种用法,然后再来分析底层实现原理。

先定义一个Runnable:

通过观察输出结果,就能知道当前运行Task的是不是虚拟线程。

也可以增加以下代码直接判断是不是虚拟线程:

Thread.ofVirtual()

手动开启虚拟线程执行任务:

自动开启虚拟线程执行任务:

两者输出结果类似,为:


根据名字可以看出确实是用的VirtualThread,但似乎跟ForkJoinPool有关,后面会分析。

我们也可以通过以下方式来创建普通线程:

输出结果为:


确实是普通线程。

还可以先得到一个ThreadFactory,然后来创建虚拟线程:


输出结果为:

还有一种更简单的API:


输出结果为:

结构化并发

在JDK21中还有一个新特性(预览版),叫做结构化并发,也会自动创建虚拟线程来运行代码,比如:


输出结果为:

Executors

还有一种和线程池类似的使用方式:


以上代码中每个任务运行时都会开启一个虚拟线程,输出�结果为:


表示有3个虚拟线程。

虚拟线程底层原理

以上大概就是使用或创建虚拟线程的几种情况了,那到底什么是虚拟线程呢?它跟线程有什么关系?它跟ForkJoinPool又有什么关系呢?

虚拟线程毕竟是虚拟的,就像虚拟机也是虚拟的,是需要真实操作系统来支撑运行的。而虚拟线程仍然是基于线程来进行调度执行的。

我们先来看看普通线程的缺点在哪,看下面代码:


假如是一个普通线程执行上述代码,在输出完“before”后,线程就会睡眠1秒,然后才会输出“after”,如果是一个线程要执行3个这样的任务,比如:


生成一个只有一个线程的线程池,用它来执行三个任务,实际上就是串行执行这三个任务,输出结果为:

但是,我们好好想想:当这个普通线程执行完第一个任务的“before”后,需要等1s才执行“after”,那能不能在等1s的过程中去执行第二个任务的“before”呢?原则上是可以的,这就是虚拟线程要优化的点。

大家好好理解一下上面的这句话,这是精髓

我们来看改成虚拟线程后的运行效果,先修改Task:

然后运行:

输出结果为:

大家运行时可能会发现有多个不同的ForkJoinPool-1-worker,那是因为我做了配置,后面会解释

不知道大家能不能看懂这个效果,我们可以发现有3个虚拟线程:VirtualThread[#21]VirtualThread[#23]VirtualThread[#24],但是只有一个线程:ForkJoinPool-1-worker-1,虽然只有一个线程,却达到了并行执行三个任务的效果,其原理就是上面所分析的:

  • 线程先执行任务1,任务1睡眠的过程中,线程会去执行任务2
  • 任务2睡眠的过程中,线程会去执行任务3
  • 任务3睡眠的过程中,线程暂时没有任务执行了
  • 过一会,任务1睡眠结束,线程继续执行任务1
  • 然后,任务2睡眠结束,线程继续执行任务2
  • 最后,任务3睡眠结束,线程继续执行任务3
  • 这样就达到了一个线程并行执行三个任务的效果,从中,我们可以看到,线程需要知道:一个任务什么时候开始睡眠了,什么时候睡眠结束了,哪个任务还没开始执行,哪个任务已经在执行中了?

    但是,任务是程序员所定义的,所以就需要虚拟线程来封装任务,而线程只关心虚拟线程即可,也就是线程负责来调度各个虚拟线程的执行,也就是来判断虚拟线程是不是睡眠了?是不是正在运行?

    我们可以把虚拟线程理解为一个对象,这个虚拟线程对象有几种状态,比如是不是睡眠中,是不是运行中,而一个线程可以支持同时运行多个虚拟线程对象,当线程发现某个虚拟线程对象睡眠时,就会去运行其他的虚拟线程对象。

    或者这么说:虚拟线程sleep了,底层的线程并一定sleep了。

    所以,虚拟线程就是线程调度的单位,一个线程可以调度很多个虚拟线程,如果有多个线程,那当然就能调度更多虚拟线程了,所以在JDK的实现中,使用ForkJoinPool来提供线程作为虚拟线程的调度者,同时由于ForkJoinPool的任务窃取机制,能进一步提高任务并行执行的效率。

    默认情况下,这个ForkJoinPool中的线程数等于CPU核心数,我们可以通过以下参数来修改:

    • -Djdk.virtualThreadScheduler.parallelism=1
    • -Djdk.virtualThreadScheduler.maxPoolSize=1

    另外,虚拟线程也不用考虑池化,因为它不像线程,开启和关闭一个线程是需要调用操作系统的,而虚拟线程跟操作系统没关系。

    再另外,JDK21中的虚拟线程也支持ThreadLocal,也就是一个虚拟线程在执行任务的过程中,也可以通过ThreadLocal来共享数据,使得我们在开发过程中就把虚拟线程当作普通线程使用就可以了。

    还有要注意的是,当任务进行网络IO、磁盘IO时也相当是sleep了,所以如果虚拟线程用到真实项目中,就能做到用少量线程支撑较高的并发,从而能大大提高项目的吞吐量,虚拟线程不是用来提速的,而是用来提高吞吐量的。

    好了,看到这里,你是不是对JDK21中的虚拟线程又有了新的理解呢?如果有,我需要正反馈,一定要帮我点赞、收藏、分享哦!

    广告

    我准备运营自己的知识星球啦,知识星球以免费答疑、优化简历、讨论技术为主,另外还能帮助大家提升工作效率、快速拿到offer、认识更多大牛、学习更多技术。

    前50名小伙伴,可以免费加入星球,私信我领取免费链接!
    微信号.JPG

    如果您愿意直接付费加入,那当然也是可以的啦

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论