VirtualThread 虚拟线程
虚拟线程在Java 19被首次引入,做为预览功能,其主要目标为让Java能够承载更高的并发,而不被OS线程拖垮。
在Java 21 中转正,成功正式特性,不再是实验功能。
线程模型
传统线程模型
在传统线程池中,在 ThreadPoolExecutor 中定义的线程,会创建一堆 Platform Thread(平台线程),它对操作系统 OS Thread的对应关系是1对1的绑定关系,也就是说线程池中的每一个线程都是真实的占用了操作系统的线程。
线程池对最大线程数做出了控制,等于控制了真实占用OS Thread的最大数量,不会无上限的创建线程,而是在达到设置的最大值时,请求进入线程池的BlockingQueue,然后进行排队等待空闲线程。
从这里可以看出,线程池存在的作用并非是提高并发能力,而是为了在使用多线程中,系统不被高峰值的请求打崩。
而这种模型下,线程在执行任务时,Platform Thread进入了阻塞状态,比如IO,Lock,wait等等,那么会连带着 OS Thread也进入阻塞状态,而虚拟线程解决了这种问题,提高了OS Thread的利用率。

虚拟线程模型
这个模型中,增加了一层Virtual Thread(虚拟线程),它存储在Java Heap中,而下面的Platform Thread则是变成了Carrier Thread(载体线程)。
当一个虚拟线程被调用执行时,JVM会从 Carrier Thread 池里取一个来讲虚拟线程的栈块复制到平台线程的 native stack,最终依旧是在平台线程上执行,平台线程也会调用一个OS线程来执行任务。
虚拟线程的优势在于,当线程阻塞后,JVM可以将虚拟线程从平台线程上卸载,从而释放所占用的OS Thread,使其可以继续执行其他虚拟线程,提高了在阻塞期间,线程的使用效率。
JVM维护了一个ForkJoinPool创建和维护的平台线程池。初始化时,平台线程数量等于CPU核心的数量,最多不会超过256个。

使用方法
不需要单独自己定义,线程的配置内部会交给JVM来自动管理,使用Executors.newVirtualThreadPerTaskExecutor()创建一个执行器即可。
当使用 try-with-resources 进行回收时,无论是submit或execute都会导致主线程的阻塞,等待子线程执行完毕后才会执行后续方法。
阻塞需要区分范围,submit或execute都会导致主线程try外部的方法被阻塞,try内部只会被Future.get阻塞。
public void test01(){
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 拿到返回值
Future<String> f1 = executor.submit(this::taskA);
// 直接执行
executor.execute(this::taskB);
System.out.println("try 内执行");
System.out.println("Result 1: " + f1.get());
System.out.println("Future.get 后执行");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("all tasks completed");
}
如果不使用 try-with-resources,也没有手动关闭close()的情况下,主线程就不会等待子线程全部执行完毕,而是会是提前关闭。
同时,你没有正确的关闭虚拟线程,也不会导致内存溢出的情况,因为根据虚拟线程的模型理解,创建虚拟线程实际是创建了一个VirtualThread的Java对象,它存储在内存堆中,当没有任何引用的时候,就会像普通对象一样被GC回收。