当前位置: 首页 > 科技观察

PerformanceMonster—JDK19VirtualThread

时间:2023-03-13 01:07:42 科技观察

一、前言生活在数字时代,我们在日常的工作和学习中或多或少遇到过这样的问题:双十一购物时,提交的订单无法响应或无法提交;查看高考成绩时,无法打开网站或无法登录网站查看分数;春运旺季,买火车票,APP一直在兜圈子,却拿不到票。“性能”是每个程序员在产品功能实现后又爱又恨的话题。未经性能测试就推出的产品就像一颗定时炸弹,随时都会被引爆;有的性能问题就像躲起来的顽皮孩子,一定时间后就会爆发。现在万物互联的物联网时代,随着社会的进步和数字城市的建立,其性能将凸显其重要性。面对各种大型设备的连接和大量设备的数据上报,物联网系统无时无刻不在承受着巨大的考验和压力。2、虚拟线程介绍虚拟线程(VirtualThreads)顾名思义,并不是传统意义上的JAVA线程。传统上,JAVA线程(以下简称平台线程)与操作系统的内核线程有着一对一的映射关系。平台线程的创建和销毁带来的开销非常大,所以JAVA使用线程池来维护平台线程,避免重复创建和销毁线程。但是,平台线程也会占用内存和CPU资源。通常在CPU和网络连接成为系统瓶颈之前,平台线程将首当其冲成为系统瓶颈。当单台服务器的硬件资源确定后,平台线程数也会受到硬件资源的限制,这也成为单台服务器吞吐量提升的主要障碍。虚拟线程是JDK而不是操作系统提供的线程的轻量级实现。它不依赖于平台线程数,也不增加额外的上下文切换开销,也不在代码的整个生命周期内运行。阻塞系统线程。整个虚拟线程的维护由JVM管理,作为一个普通的JAVA对象存储在RAM中。那么就意味着几个虚拟线程可以在同一个系统线程上运行应用程序代码,只有在虚拟线程执行时才会消耗系统线程,系统线程在等待和休眠时不会阻塞。虚拟线程是一种非常廉价且丰富的线程。可以说虚拟线程的数量是一个近乎无限的线程数量。其硬件利用率接近最佳。在相同硬件配置的服务器情况下,虚拟线程比使用平台线程的并发度更高,提高整体应用吞吐量。如果平台线程和系统线程按1:1的方式调度,则虚拟线程采用M:N的调度方式,大量的虚拟线程M运行在较少的系统线程N上。那么JVM是如何调度虚拟线程的呢?首先创建一个虚拟线程,然后JVM会在平台线程上加载虚拟线程,平台线程会绑定一个系统线程。JVM将使用调度程序在使用调度线程的虚拟线程中执行任务。任务执行完毕后,清除上下文变量,将调度线程返回给调度器等待下一个任务。3、虚拟线程VS平台线程虚拟线程的使用其实很简单。它与平台线程的使用基本相同。唯一不同的是,创建虚拟线程时,需要调用newVirtualThreadPerTaskExecutor()来创建虚拟线程。下面我创建三个线程模拟高并发IO,打印系统线程数得到三个线程对处理100000个累计计数的时长。?主程序:主程序使用定时任务打印每秒消耗的系统线程数。第一种方式不限制使用普通线程(平台线程),不考虑OOM情况:?三次运行结果:普通线程(平台线程)耗时(三次):9584ms、10189ms、9586ms对于普通线程(平台线程Thread)count:100000初始占用系统线程数:9;占用系统线程数峰值:20027、19137、19140第二种方式是使用线程池方式创建普通线程(平台线程),考虑到OOM的情况,threads在池中创建1000个普通线程:?结果三运行(因为运行时间太长,初始线程数无法完全截图):线程池模式1000个普通线程(平台线程)耗时(三次):100165ms、100146ms、100159ms线程池模式1000个普通线程(platformthread)count:100000初始占用系统线程数:9;占用系统线程数峰值:1009、1009、1009第三种方式,使用虚拟线程方式创建100000个虚拟线程:?三次运行结果:虚拟线程耗时(三次):2290ms、2523ms、2412ms虚拟线程(platformthread)count:100000初始占用系统线程数:9;峰值占用系统线程线程数:16根据释放机制,峰值占用系统线程数将从16个逐渐下降到9个。由于释放需要一定的时间,所以没有完整的释放系统线程截图采取。从上表可以看出,线程池模式处理10万个累计并发处理的时间是虚拟线程的50倍;在不考虑服务内存OOM的情况下,普通线程模式占用大量系统线程处理10万个累计并发处理的时间也是虚拟线程的5倍。虚拟线程只占用7个系统线程就可以处理10万累计并发,这已经不能说是并发性能的巨大提升,而是并发怪兽,一场性能革命!但虚拟线程的运行速度并不比平台线程快,因此它们不能用于减少延迟。4、虚拟线程的使用场景那么什么时候可以使用虚拟线程呢?应用系统有大量的并发任务(超过几千个并发任务),这些任务也需要大量的等待时间;在IO密集型场景下,工作负载不受CPU限制。如何改造当前的线程池?直接用虚拟线程代替线程池。如果代码中使用了CompletableFuture,直接将异步执行任务线程池替换为:Executors.newVirtualThreadPerTaskExecutor()。虚拟线程非常轻量,不需要创建池,直接创建虚拟线程即可;synchronized改为ReentrantLock以减少固定到平台线程的虚拟线程数;ThreadLocal在虚拟线程中的使用与平台线程一致,但是创建了大量的虚拟线程,每个虚拟线程都有一个ThreadLocal实例及其引用的数据,会给内存带来很大的负担。5.小结在万物互联的今天,设备连接数越来越多,物联网平台的并发量越来越大,已经不是我们可以忽视的问题。JDK19中的性能怪兽——虚拟线程,给我们带来了新的方向。解决物联网平台并发问题。虚拟线程中还有很多前沿技术和设计思想可以深入探索、学习和借鉴,这需要我们不断探索和实践,完善我们的OneNET平台,以应对未来无限的机遇和挑战。