在当今高速发展的计算机领域,多线程编程已经成为了一种普遍的技术选择。而虚拟线程作为多线程编程中的一个重要概念,正逐渐引起了人们的关注。虚拟线程不仅能够提高程序的性能和响应能力,还能有效地利用计算机的资源。然而,虚拟线程的实现原理及其优缺点却是一个备受关注的话题。本文将深入探讨虚拟线程的实现原理,并分析其在实际应用中的优点和缺点。通过对虚拟线程的全面了解,我们将能够更好地应用它来优化我们的程序,并在多线程编程中取得更好的效果。
二、背景在传统的Java线程模型中,每个线程都需要占用操作系统的线程资源。线程的创建和销毁都需要消耗一定的时间和系统资源,而线程的上下文切换也会带来额外的开销。当需要处理大量的并发任务时,线程的数量会急剧增加,从而导致系统资源的浪费和性能下降。
线程池通过复用已创建的线程,避免了频繁创建和销毁线程的开销,并提高程序的性能和响应速度。但任务的IO操作和并发锁导致线程等待或阻塞,产生上下文切换,从而导致CPU资源的浪费和应用性能下降。尤其是IO密集型服务:CPU与IO速度不匹配,造成CPU资源严重浪费。
三、虚拟线程介绍虚拟线程是JDK中的一种轻量级线程,运行时仅是一个Java对象,与传统的线程相比,在创建、销毁和调度方面的开销更小。
系统线程:操作系统调度的基本单位,由操作系统管理;
平台线程:Java虚拟机的线程,与操作系统一对一映射,受操作系统和硬件设备限制;
虚拟线程:一种轻量级线程【VirtualThread】 ;
载体线程:执行虚拟线程任务的平台线程。
3.1、使用直接创建虚拟线程Thread vt = Thread.startVirtualThread(() -> { System.out.println("hello wolrd virtual thread");});通过虚拟线程的 ThreadFactory 创建虚拟线程ThreadFactory tf = Thread.ofVirtual().factory();Thread vt = tf.newThread(() -> { System.out.println("hello wolrd virtual thread");});vt.start();虚拟线程池Executors.newVirtualThreadPerTaskExecutor();// 通过虚拟线程的ThreadFactory创建虚拟线程3.2、状态3.3、原理虚拟线程由executor、Continuation和state组成,其中Continuation是虚拟线程的核心,对task封装,提供task等待或阻塞时出让载体线程和task恢复运行时挂载到平台线程的功能。executor: 使用了 FIFO 模式的 ForkJoinPool 作为虚拟线程的调度器。
在虚拟线程中,任务执行时会调用Continuation的run()方法。该方法首先执行部分任务代码,然后尝试网络IO。如果IO操作是阻塞的,会导致Continuation执行yield操作,让出控制权。如果yield操作成功,虚拟线程会从载体线程中解绑(unmount),此时载体线程的栈数据会移动到Continuation的栈数据帧中,并保存在堆内存中。此时,虚拟线程的任务完成,但虚拟线程和Continuation并没有终结和释放,而是将载体线程释放到执行器中,等待新的任务。如果yield操作失败,说明无法获取锁,Continuation会对载体线程进行Park调用,将载体线程阻塞。此时,虚拟线程和载体线程都会被阻塞。
如上图:6个任务被封装到6个虚拟线程通过线程池[2个平台线程]执行,任务中有网络IO:平台线程[thead-1]执行task1阻塞时,卸载task1后执行task3,执行task3也阻塞时执行task5。所有任务在等待网络IO后,task1被挂载到平台线程[thead-2]上运行,直到任务结束。共享等待时间,6个任务完成耗时大大缩短。
需要注意的是,虚拟线程和Continuation的运行流程是由具体的实现机制决定的,上述描述是一种可能的情况,具体的实现可能会有所不同。此外,Continuation是一种用于实现轻量级线程的技术,它可以实现线程的暂停和恢复,从而提供更高效的并发执行能力。
3.4、优点创建快:虚拟线程创建不需要创建系统线程,只需按照创建对象的流程初始化虚拟线程的属性。应用能快速创建虚拟线程,且开销小,使得应用程序可以创建大量的虚拟线程和有限的平台线程来处理高并发任务。尽量不阻塞平台线程:当虚拟线程的任务阻塞或等待时,JVM快速将其从载体线程解除挂载,并调度其他虚拟线程挂载到该载体线程,无需上下文切换,大大提升CPU利用率。等待时间少: 虚拟线程出让平台,与其他等待状态的虚拟线程共同等待,等待时间无叠加。销毁快:当任务完成后,JVM销毁虚拟线程的堆栈、局部变量等,无需清理平台线程。3.5、缺点额外CPU消耗:虚拟线程被挂载时栈帧数据从堆区复制到平台线程的栈(载体线程),任务阻塞后虚拟线程卸载,从载体线程的栈数据帧复制到堆区。挂载和卸载过程中消耗CPU执行复制操作局限性:当任务调用本地方法、外部函数、synchronized块或方法被阻塞时导致yield操作失败,则虚拟线程阻塞在载体线程3.6、测试选取了业务中常见逻辑的接口:依赖四个外部数据,其中3个API,1个数据库,通过线程池完成外部数据获取,然后业务站点中处理数据并返回。
基于平台线程获取外部数据 经多次调整线程池参数,获取最优结果基于虚拟线程获取外部数据通过优化线程池配置,在IO密集型应用中,我们成功将应用的吞吐量从79提升到228,增长了188%。同时,接口的耗时也从650ms降低到231ms,降低了64.46%。尽管各个服务可能存在一些差异,但使用虚拟线程的方式显著提升了应用的吞吐量,并降低了部分响应的耗时。
四、总结本文介绍了虚拟线程的概念、实现原理以及优缺点。虚拟线程是一种高效的轻量级线程技术,特别适用于IO密集型服务,可以显著提升性能。我们的优化措施不仅仅关注吞吐量的增长,还注重降低接口耗时,以提供更快速、高效的应用体验。
作者简介长栓,信也科技资深研发。
来源-微信公众号:拍码场
出处:https://mp.weixin.qq.com/s/vkHGIbxAXjjOSnIo0IzO4w