虚拟线程

268 阅读4分钟

1、概述

虚拟线程:是 JDK 21 推出的一种新的轻量级线程实现,它由 Java 运行时环境(JRE)管理,而不是由操作系统内核管理。这使得可以轻松创建和管理数以百万计的线程,而不会像传统的平台线程那样受到资源限制。

虚拟线程与平台线程的对比:

对比虚拟线程平台线程
调度由 JVM 管理由操作系统管理
创建成本创建成本低创建成本高
线程数量数百万计受限于操作系统限制,几千到几万不等
内存占用几十 KB1 MB 左右
上下文切换无系统级上下文切换有系统级上下文切换,成本较高
适用场景I/O 密集型CPU 密集型

虚拟线程的优势:

  1. 资源效率:虚拟线程在内存使用上更为高效。
  2. 简化线程管理:虚拟线程的创建和管理过程更为简便,通过工厂方法可以轻松创建,无需手动管理线程资源。
  3. 避免线程爆炸:由于资源消耗低,虚拟线程可以处理大量并发任务,而不必担心资源耗尽。
  4. 协作调度:虚拟线程采用协作调度模型,减少了锁竞争和上下文切换的开销,提升了多线程程序的性能。
  5. 避免阻塞:虚拟线程在遇到阻塞操作时可以释放执行权,允许其他线程执行,提高了程序的响应性。

2、虚拟线程的使用

1)Thread.ofVirtual():返回一个 Thread.Builder 对象,允许设置线程名称、线程组等属性,最后通过 start(Runnable task) 来启动线程。

Thread.ofVirtual()
    .name("VirtualThread-Test")
    .start(() -> System.out.println("Hello Virtual Thread"));

2)Thread.startVirtualThread():接收一个 Runnable 对象作为参数,创建并立即启动一个虚拟线程,并返回一个 Thread 类的实例。

Thread thread = Thread.startVirtualThread(() -> System.out.println("Hello Virtual Thread"));

3)Executors.newVirtualThreadPerTaskExecutor():创建一个 ExecutorService,它为每个提交的任务创建一个新的虚拟线程。

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> System.out.println("Task 1 in a virtual thread"));
executor.submit(() -> System.out.println("Task 2 in a virtual thread"));
executor.submit(() -> System.out.println("Task 3 in a virtual thread"));

3、虚拟线程的原理

系统线程、平台线程、虚拟线程之间的关系:

  • 平台线程是由操作系统管理,一个平台线程(Platform Thread)通常对应于一个系统线程(OS Thread)。
  • 虚拟线程是由 JVM 管理,一个平台线程(Platform Thread)可以运行多个虚拟线程(Virtual Thread)。

为了验证虚拟线程(Virtual Thread)和平台线程(Platform Thread)的关系,我们可以执行以下代码:

Thread.ofPlatform().start(() -> System.out.println(Thread.currentThread()));
for (int i = 0; i < 1000; i++) {
    Thread.ofVirtual().start(() -> System.out.println(Thread.currentThread()));
}

运行结果:

Thread[#30,Thread-0,5,main]
VirtualThread[#31]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#36]/runnable@ForkJoinPool-1-worker-3
VirtualThread[#33]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#39]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#40]/runnable@ForkJoinPool-1-worker-1
...
  • Thread[#30,Thread-0,5,main]:一个编号为 30、名为 Thread-0、优先级为 5、属于 main 线程组的线程。
  • VirtualThread[#31]/runnable@ForkJoinPool-1-worker-1:一个编号为 31、状态为 runnable、由 ForkJoinPool-1 线程池中的一个工作线程(worker-1)执行的虚拟线程。
  • VirtualThread[#33]/runnable@ForkJoinPool-1-worker-2:一个编号为 33、状态为 runnable、由 ForkJoinPool-1 线程池中的一个工作线程(worker-2)执行的虚拟线程。
  • VirtualThread[#36]/runnable@ForkJoinPool-1-worker-3:一个编号为 36、状态为 runnable、由 ForkJoinPool-1 线程池中的一个工作线程(worker-3)执行的虚拟线程。 由此可知,虚拟线程是在 ForkJoinPool 线程池中的 worker 线程中执行,而多个虚拟线程会复用 worker 线程,如下图:

image.png ForkJoinPool 是 Java 中的一个线程池实现,它使用工作窃取算法来平衡线程的工作负载。每个工作线程都有自己的任务队列,当某个线程完成了自己的任务,它会尝试从其他线程的队列中窃取任务来执行,通常是从队列的尾部获取,以实现负载均衡。

虚拟线程通过与 ForkJoinPool 结合使用,可以充分利用多核 CPU 的优势,将一个大任务分解成多个小任务,并在多个处理器核心上并行执行,从而提高程序的并发性能和响应能力。