2

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

 11 months ago
source link: https://blog.csdn.net/u011919808/article/details/133427003
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

爱读源码的大都督 已于 2023-09-30 12:04:06 修改 349
文章标签: java

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

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

先定义一个Runnable:

939eedb2bae9b8c9edabd080cd47133a.png

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

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

464bc29fee74bd5d78ef131b1dc78731.png

Thread.ofVirtual()

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

da51867318f8f249d45513a8ec14189d.png

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

774f6836109c111a7e3bc136704620d2.png

两者输出结果类似,为:

d620cfb5a9829cf7457094ec71a8db65.png

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

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

9ae1a6a54a1f12eefca4c9b9283768d7.png

输出结果为:

ce4a44567de0e7acb52d02f0965673a7.png

确实是普通线程。

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

bacb0556106539f2c1afe8b175f59cad.png

输出结果为:

8ad1a2aadd26c55224a5d0170c25b616.png

还有一种更简单的API:

a82c2ad4392425defb93040fc1dcaf46.png

输出结果为:

93cfea7216a80aa90c6e8f8e0aa74554.png

结构化并发

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

a1a88025f42a292eb1132fb20e8a2a06.png

输出结果为:

a632c29926d86cc04ea772d8ed678a38.png

Executors

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

56ad4a990a2fb8c1f3d7219935ecb93a.png

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

ecde2589f3b9fc6668b204c43c621cdb.png

表示有3个虚拟线程。

虚拟线程底层原理

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

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

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

e3419c98aae642c561a5ad9579fb572a.png

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

1bc66bae4b966d52fe5fbff473cd369d.png

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

dd388acaad687506b9ab7ece69c996ba.png

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

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

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

a752972825623d3899dd9d72a2a2233a.png

然后运行:

bb2749412925a26e68060440cfcfb4f1.png

输出结果为:

ea0ec767c07e50a97dda66c21afffdf4.png

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

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

  1. 线程先执行任务1,任务1睡眠的过程中,线程会去执行任务2
  2. 任务2睡眠的过程中,线程会去执行任务3
  3. 任务3睡眠的过程中,线程暂时没有任务执行了
  4. 过一会,任务1睡眠结束,线程继续执行任务1
  5. 然后,任务2睡眠结束,线程继续执行任务2
  6. 最后,任务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

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

e7deb46b71384f01848cdbaf6512b849.jpeg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK