1、概述
虚拟线程:是 JDK 21 推出的一种新的轻量级线程实现,它由 Java 运行时环境(JRE)管理,而不是由操作系统内核管理。这使得可以轻松创建和管理数以百万计的线程,而不会像传统的平台线程那样受到资源限制。
虚拟线程与平台线程的对比:
| 对比 | 虚拟线程 | 平台线程 |
|---|---|---|
| 调度 | 由 JVM 管理 | 由操作系统管理 |
| 创建成本 | 创建成本低 | 创建成本高 |
| 线程数量 | 数百万计 | 受限于操作系统限制,几千到几万不等 |
| 内存占用 | 几十 KB | 1 MB 左右 |
| 上下文切换 | 无系统级上下文切换 | 有系统级上下文切换,成本较高 |
| 适用场景 | I/O 密集型 | CPU 密集型 |
虚拟线程的优势:
- 资源效率:虚拟线程在内存使用上更为高效。
- 简化线程管理:虚拟线程的创建和管理过程更为简便,通过工厂方法可以轻松创建,无需手动管理线程资源。
- 避免线程爆炸:由于资源消耗低,虚拟线程可以处理大量并发任务,而不必担心资源耗尽。
- 协作调度:虚拟线程采用协作调度模型,减少了锁竞争和上下文切换的开销,提升了多线程程序的性能。
- 避免阻塞:虚拟线程在遇到阻塞操作时可以释放执行权,允许其他线程执行,提高了程序的响应性。
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 线程,如下图:
ForkJoinPool 是 Java 中的一个线程池实现,它使用工作窃取算法来平衡线程的工作负载。每个工作线程都有自己的任务队列,当某个线程完成了自己的任务,它会尝试从其他线程的队列中窃取任务来执行,通常是从队列的尾部获取,以实现负载均衡。
虚拟线程通过与 ForkJoinPool 结合使用,可以充分利用多核 CPU 的优势,将一个大任务分解成多个小任务,并在多个处理器核心上并行执行,从而提高程序的并发性能和响应能力。