首页 > IT基础架构 > 服务器/存储 > 虚拟线程原理及性能分析|得物技术
来源:得物技术
目录
一、背景
二、为了提升吞吐性能,我们所做的优化
1. 串行模式
2. 线程池 +Future 异步调用
3. 线程池 +CompletableFuture 异步调用
三、一请求一线程的模型
四、虚拟线程
1. 线程术语定义
2. 虚拟线程定义
3. 虚拟线程创建
4. 虚拟线程实现原理
5. 虚拟线程内存占用评估
6. 虚拟线程的局限及使用建议
7. 虚拟线程适用场景
五、虚拟线程压测性能分析
1. 测试流程
2. 衡量指标
3. Tomcat+普通线程池
4. WebFlux
5. Tomcat+虚拟线程池
六、总结
一
背景
二
为了提升吞吐性能,我们所做的优化
串行模式


线程池+Future异步调用


线程池+CompletableFuture异步调用
CompletableFuture对调用流程进行编排,降低依赖之间的阻塞。
线程资源浪费瓶颈始终在 IO 等待上,导致 CPU 资源利用率较低。目前大部分服务是 IO 密集型服务,一次请求的处理耗时大部分都消耗在等待下游 RPC,数据库查询的 IO 等待中,此时线程仍然只能阻塞等待结果返回,导致 CPU 的利用率很低。三
一请求一线程的模型

扩大服务最大线程数,简单有效,由于存在下列问题,导致平台线程有最大数量限制,不能大量扩充。
垂直扩展,升级机器配置,水平扩展,增加服务节点,也就是俗称的升配扩容大法,效果好,也是最常见的方案,缺点是会增加成本,同时有些场景下扩容并不能 100% 解决问题。采用异步/响应式编程方案,例如 RPC NIO 异步调用,WebFlux,Rx-Java 等非阻塞的基于 Ractor 模型的框架,使用事件驱动使得少量线程即可实现高吞吐的请求处理,拥有较好的性能与优秀的资源利用,缺点是学习成本较高兼容性问题较大,编码风格与目前的一请求一线程的模型差异较大,理解难度大,同时对于代码的调试比较困难。JDK21 中的虚拟线程可能给出了答案, JDK 提供了与 Thread 完全一致的抽象 Virtual Thread 来应对这种经常阻塞的情况,阻塞仍然是会阻塞,但是换了阻塞的对象,由昂贵的平台线程阻塞改为了成本很低的虚拟线程的阻塞,当代码调用到阻塞 API 例如 IO,同步,Sleep 等操作时,JVM 会自动把 Virtual Thread 从平台线程上卸载,平台线程就会去处理下一个虚拟线程,通过这种方式,提升了平台线程的利用率,让平台线程不再阻塞在等待上,从底层实现了少量平台线程就可以处理大量请求,提高了服务吞吐和 CPU 的利用率。四
虚拟线程
线程术语定义
操作系统线程(OS Thread):由操作系统管理,是操作系统调度的基本单位。平台线程(Platform Thread):Java.Lang.Thread 类的每个实例,都是一个平台线程,是 Java 对操作系统线程的包装,与操作系统是 1:1 映射。虚拟线程(Virtual Thread):一种轻量级,由 JVM 管理的线程。对应的实例 java.lang.VirtualThread 这个类。载体线程(Carrier Thread):指真正负责执行虚拟线程中任务的平台线程。一个虚拟线程装载到一个平台线程之后,那么这个平台线程就被称为虚拟线程的载体线程。虚拟线程定义
平台线程。平台线程在底层操作系统线程上运行 Java 代码,并在代码的整个生命周期内独占操作系统线程,平台线程实例本质是由系统内核的线程调度程序进行调度,并且平台线程的数量受限于操作系统线程的数量。
虚拟线程创建
方法一:直接创建虚拟线程
Thread vt = Thread.startVirtualThread(() -> { System.out.println("hello wolrd virtual thread");});Thread.ofVirtual().unstarted(() -> { System.out.println("hello wolrd virtual thread");});vt.start();方法三:通过虚拟线程的 ThreadFactory 创建虚拟线程
ThreadFactory tf = Thread.ofVirtual().factory();Thread vt = tf.newThread(() -> { System.out.println("Start virtual thread..."); Thread.sleep(1000); System.out.println("End virtual thread. ");});vt.start();方法四:Executors.newVirtualThreadPer
-TaskExecutor()
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();executor.submit(() -> {System.out.println("Start virtual thread...");Thread.sleep(1000);System.out.println("End virtual thread.");return true;});
虚拟线程实现原理
virtual thread =continuation+scheduler+runnableContinuation 实例中:yield 操作进行阻塞,虚拟线程会从平台线程卸载。Continuation.run 会从阻塞点继续执行。Scheduler 也就是执行器,由它将任务提交到具体的载体线程池中执行。Runnable 则是真正的任务包装器,由 Scheduler 负责提交到载体线程池中执行。mount(挂载),取消分配平台线程的操作称为 unmount(卸载):mount 操作:虚拟线程挂载到平台线程,虚拟线程中包装的 Continuation 堆栈帧数据会被拷贝到平台线程的线程栈,这是一个从堆复制到栈的过程。unmount 操作:虚拟线程从平台线程卸载,此时虚拟线程的任务还没有执行完成,所以虚拟线程中包装的 Continuation 栈数据帧会会留在堆内存中。



Continuation 的 yield 操作让出控制权,等待虚拟线程重新分配载体线程并且执行,具体见下面的代码: ReentrantLock lock = new ReentrantLock(); Thread.startVirtualThread(() -> { lock.lock(); }); // 确保锁已经被上面的虚拟线程持有 Thread.sleep(1000); Thread.startVirtualThread(() -> { System.out.println("first"); 会触发Continuation的yield操作 lock.lock(); try { System.out.println("second"); } finally { lock.unlock(); } System.out.println("third"); }); Thread.sleep(Long.MAX_VALUE); }Continuation 的 yield 操作让出控制权,如果 yield 操作成功,会从载体线程 unmount,载体线程栈数据会移动到 Continuation 栈的数据帧中,保存在堆内存中,虚拟线程任务完成,此时虚拟线程和 Continuation 还没有终结和释放,载体线程被释放到执行器中等待新的任务;如果 Continuation 的 yield 操作失败,则会对载体线程进行 Park 调用,阻塞在载体线程上,此时虚拟线程和载体线程同时会被阻塞,本地方法,Synchronized 修饰的同步方法都会导致 yield 失败。
Continuation#run() 恢复任务执行。
Continuation 的 yield 操作进行阻塞。当任务需要解除阻塞继续执行的时候,则调用 Continuation 的 run 恢复执行。--add-exports java.base/jdk.internal.vm=ALL-UNNAMED 可以在本地运行。ContinuationScope scope = new ContinuationScope("scope");Continuation continuation = new Continuation(scope, () -> { System.out.println("before yield开始"); Continuation.yield(scope); System.out.println("after yield 结束");});System.out.println("1 run");// 第一次执行Continuation.runcontinuation.run();System.out.println("2 run");// 第二次执行Continuation.runcontinuation.run();System.out.println("Done");
Continuation 实例进行 yield 调用后,再次调用其 run 方法就可以从 yield 的调用之处继续往下执行,从而实现了程序的中断和恢复。虚拟线程内存占用评估
private static final int COUNT = 4000;/*** -XX:NativeMemoryTracking=detail** @param args args*/public static void main(String[] args) throws Exception {for (int i = 0; i < COUNT; i++) {new Thread(() -> {try {Thread.sleep(Long.MAX_VALUE);} catch (Exception e) {e.printStackTrace();}}, String.valueOf(i)).start();}Thread.sleep(Long.MAX_VALUE);}

private static final int COUNT = 4000;/*** -XX:NativeMemoryTracking=detail** @param args args*/public static void main(String[] args) throws Exception {for (int i = 0; i < COUNT; i++) {Thread.startVirtualThread(() -> {try {Thread.sleep(Long.MAX_VALUE);} catch (Exception e) {e.printStackTrace();}});}Thread.sleep(Long.MAX_VALUE);}

虚拟线程的局限及使用建议
native 方法或者外部方法 (Foreign Function & Memory API,jep 424 ) 调用不能进行 yield 操作,此时载体线程会被阻塞。当运行在 synchronized 修饰的代码块或者方法时,不能进行 yield 操作,此时载体线程会被阻塞,推荐使用 ReentrantLock。虚拟线程适用场景
Thread-per-request (一请求一线程)风格的应用程序,例如主流的 Tomcat 线程模型或者基于类似线程模型实现的 SpringMVC 框架 ,这些应用只需要小小的改动就可以带来巨大的吞吐提升。
五
虚拟线程压测性能分析
场景一:在 Spring Boot 中使用内嵌的 Tomcat 去处理 Http 请求,使用默认的平台线程池作为 Tomcat 的请求处理线程池。场景二:使用 Spring -WebFlux 创建基于事件循环模型的应用程序,进行响应式请求处理。场景三:在 Spring Boot 中使用内嵌的 Tomcat 去处理 Http 请求,使用虚拟线程池作为 Tomcat 的请求处理线程池 (Tomcat已支持虚拟线程)。测试流程

衡量指标
Tomcat+普通线程池


WebFlux
@Beanpublic WebClient slowServerClient() {return WebClient.builder().baseUrl("http://127.0.0.1:8000").build();}@Beanpublic RouterFunction<ServerResponse> routes(WebClient slowServerClient) {return route(GET("/"), (ServerRequest req) -> ok().body(slowServerClient.get().exchangeToFlux(resp -> resp.bodyToFlux(Object.class)),Object.class));}

Tomcat+虚拟线程池
--enable-preview,同时 Tomcat 在 10 版本已支持虚拟线程,我们只需要替换 Tomcat 的平台线程池为虚拟线程池即可。
@Beanpublic TomcatProtocolHandlerCustomizer<?> protocolHandler() {return protocolHandler ->protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());}private final RestTemplate restTemplate;@GetMappingpublic ResponseEntity<Object> callSlowServer(){return restTemplate.getForEntity("http://127.0.0.1:8000", Object.class);}


最大的优势就是我们没有修改代码或采用任何反应式技术,唯一更改是将线程池替换为虚拟线程。虽然改动较小,但与使用线程池相比,性能结果得到了显著改善。六
总结
一请求一线程的模型这种方式易于理解和编程实现,也易于调试和性能调优。https://openjdk.org/jeps/444
https://zhuanlan.zhihu.com/p/514719325
https://www.vlts.cn/post/virtual-thread-source-code#%E5%89%8D%E6%8F%90
https://zhuanlan.zhihu.com/p/499342616
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70027824/viewspace-2993461/ ,如需转载,请注明出处,否则将追究法律责任。