JDK21中虚拟线程到底是什么以及用法总结(看完便知)
作者:爱读源码的大都督
本文涉及到的技术:虚拟线程、结构化并发、线程池、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中虚拟线程到底的文章就介绍到这了,更多相关JDK21虚拟线程用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!