Java19 正式 GA,看虚拟线程如何大幅提高系统吞吐量

Java19引入了虚拟线程(Virtual Thread),旨在提高并发性能,尤其适合高并发、IO密集型场景。虚拟线程与平台线程(M:N调度)运行,减少了系统线程的创建和上下文切换开销。通过对比测试,虚拟线程相比于传统线程在执行大量并发任务时表现出更高的效率和更低的系统线程使用。此外,虚拟线程与Go协程类似,但在Java中创建和管理虚拟线程需要更多代码。Java虚拟线程适用于大量并发任务且不受CPU约束的工作负载,通过减少阻塞带来的性能损失,提升了服务端应用程序的吞吐量。
摘要由CSDN通过智能技术生成

今天Java19 正式发布,带来了一个 Java 开发者垂涎已久的新特性—— 虚拟线程。在 Java 有这个新特性之前,Go语言的协程风靡已久,在并发编程领域可以说是叱咤风云。随着国内 Go 语言的快速发展与推广,协程好像成为了一个世界上最好语言的必备特性之一。Java19 虚拟线程就是来弥补这个空白的。本文将通过对虚拟线程的介绍,以及与 Go 协程的对比来带大家尝鲜 Java19 虚拟线程。

本文要点:
  • Java 线程模型
  • 平台线程与虚拟线程性能对比
  • Java 虚拟线程与 Go 协程对比
  • 如何使用虚拟线程

Java 线程模型

java 线程 与 虚拟线程

我们常用的 Java 线程与系统内核线程是一一对应的,系统内核的线程调度程序负责调度 Java 线程。为了增加应用程序的性能,我们会增加越来越多的 Java 线程,显然系统调度 Java 线程时,会占据不少资源去处理线程上下文切换。

在这里插入图片描述

近几十年来,我们一直依赖上述多线程模型来解决 Java 并发编程的问题。为了增加系统的吞吐量,我们要不断增加线程的数量,但机器的线程是昂贵的、可用线程数量也是有限的。即使我们使用了各种线程池来最大化线程的性价比,但是线程往往会在 CPU、网络或者内存资源耗尽之前成为我们应用程序的性能提升瓶颈,不能最大限度的释放硬件应该具有的性能。

在这里插入图片描述

为了解决这个问题 Java19 引入了虚拟线程(Virtual Thread)。在 Java19 中,之前我们常用的线程叫做平台线程(platform thread),与系统内核线程仍然是一一对应的。其中大量(M)的虚拟线程在较小数量(N)的平台线程(与操作系统线程一一对应)上运行(M:N调度)。多个虚拟线程会被 JVM 调度到某一个平台线程上执行,一个平台线程同时只会执行一个虚拟线程。

创建 Java 虚拟线程

新增线程相关 API

Thread.ofVirtual()Thread.ofPlatform()是创建虚拟和平台线程的新API:

//输出线程ID 包括虚拟线程和系统线程 Thread.getId() 从jdk19废弃
Runnable runnable = () -> System.out.println(Thread.currentThread().threadId());
//创建虚拟线程
Thread thread = Thread.ofVirtual().name("testVT").unstarted(runnable);
testVT.start();
//创建虚平台线程
Thread testPT = Thread.ofPlatform().name("testPT").unstarted(runnable);
testPT.start();

使用Thread.startVirtualThread(Runnable)快速创建虚拟线程并启动:

//输出线程ID 包括虚拟线程和系统线程
Runnable runnable = () -> System.out.println(Thread.currentThread().threadId());
Thread thread = Thread.startVirtualThread(runnable);

Thread.isVirtual()判断线程是否为虚拟线程:

//输出线程ID 包括虚拟线程和系统线程
Runnable runnable = () -> System.out.println(Thread.currentThread().isVirtual());
Thread thread = Thread.startVirtualThread(runnable);

Thread.joinThread.sleep等待虚拟线程结束、使虚拟线程 sleep:

Runnable runnable = () -> System.out.println(Thread.sleep(10));
Thread thread = Thread.startVirtualThread(runnable);
//等待虚拟线程结束
thread.join();

Executors.newVirtualThreadPerTaskExecutor()创建一个 ExecutorService,该 ExecutorService 为每个任务创建一个新的虚拟线程:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
   
  executor.submit(() -> System.out.println("hello"));
}

支持与使用线程池和 ExecutorService 的现有代码互相替换、迁移。

注意:

因为虚拟线程在 Java19 中是预览特性,所以本文出现的代码需按以下方式运行:

  • 使用javac --release 19 --enable-preview Main.java编译程序,并使用java --enable-preview Main运行;
  • 或者使用java --source 19 --enable-preview Main.java运行程序;

是骡子是马

既然是为了解决平台线程的问题,那我们就直接测试平台线程与虚拟线程的性能。

测试内容很简单,并行执行一万个 sleep 一秒的任务,对比总的执行时间和所用系统线程数量。

为了监控测试所用系统线程的数量,编写如下代码:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() -> {
   
  ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
  ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
  System.out.println(threadInfo.length + " os thread");
}, 1, 1, TimeUnit.SECONDS);

调度线程池每一秒钟获取并打印系统线程数量,便于观察线程的数量。

public static void main(String[] args) {
   
  //记录系统线程数
  ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
  scheduledExecutorService.scheduleAtFixedRate(() -> {
   
    ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
    ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
    System.out.println(threadInfo.length + " os thread");
  }, 1, 1, TimeUnit.SECONDS);

  long l = System.currentTimeMillis();
  try(var executor = Executors.newCachedThreadPool()) {
   
    IntStream.range(0, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值