虚拟线程常见问题总结
虚拟线程常见问题总结
虚拟线程是Java 19中引入的一项预览特性,旨在简化并发编程并提高应用程序的性能。与传统的平台线程相比,虚拟线程具有轻量级、易于管理和资源消耗低等优势,特别适合处理I/O密集型任务。本文将详细介绍虚拟线程的基本概念、使用方法、性能优势以及底层实现原理。
什么是虚拟线程?
虚拟线程(Virtual Thread)是JDK而不是OS实现的轻量级线程(Lightweight Process,LWP),由JVM调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。
虚拟线程和平台线程有什么关系?
在引入虚拟线程之前,java.lang.Thread
包已经支持所谓的平台线程(Platform Thread),也就是没有虚拟线程之前,我们一直使用的线程。JVM调度程序通过平台线程(载体线程)来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程(多个虚拟线程挂载在一个平台线程上),当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程。
虚拟线程、平台线程和系统内核线程的关系图如下所示(图源:How to Use Java 19 Virtual Threads):
关于平台线程和系统内核线程的对应关系多提一点:在Windows和Linux等主流操作系统中,Java线程采用的是一对一的线程模型,也就是一个平台线程对应一个系统内核线程。Solaris系统是一个特例,HotSpot VM在Solaris上支持多对多和一对一。具体可以参考R大的回答:JVM中的线程模型是用户级的么?
虚拟线程有什么优点和缺点?
优点
- 非常轻量级:可以在单个线程中创建成百上千个虚拟线程而不会导致过多的线程创建和上下文切换。
- 简化异步编程:虚拟线程可以简化异步编程,使代码更易于理解和维护。它可以将异步代码编写得更像同步代码,避免了回调地狱(Callback Hell)。
- 减少资源开销:由于虚拟线程是由JVM实现的,它能够更高效地利用底层资源,例如CPU和内存。虚拟线程的上下文切换比平台线程更轻量,因此能够更好地支持高并发场景。
缺点
- 不适用于计算密集型任务:虚拟线程适用于I/O密集型任务,但不适用于计算密集型任务,因为密集型计算始终需要CPU资源作为支持。
- 与某些第三方库不兼容:虽然虚拟线程设计时考虑了与现有代码的兼容性,但某些依赖平台线程特性的第三方库可能不完全兼容虚拟线程。
如何创建虚拟线程?
官方提供了以下四种方式创建虚拟线程:
- 使用
Thread.startVirtualThread()
创建 - 使用
Thread.ofVirtual()
创建 - 使用
ThreadFactory
创建 - 使用
Executors.newVirtualThreadPerTaskExecutor()
创建
1. 使用Thread.startVirtualThread()
创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
Thread.startVirtualThread(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
2. 使用Thread.ofVirtual()
创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
// 创建不启动
Thread unStarted = Thread.ofVirtual().unstarted(customThread);
unStarted.start();
// 创建直接启动
Thread.ofVirtual().start(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
3. 使用ThreadFactory
创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(customThread);
thread.start();
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
4. 使用Executors.newVirtualThreadPerTaskExecutor()
创建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
虚拟线程和平台线程性能对比
通过多线程和虚拟线程的方式处理相同的任务,对比创建的系统线程数和处理耗时。
说明:统计创建的系统线程中部分为后台线程(比如GC线程),两种场景下都一样,所以并不影响对比。
测试代码:
public class VirtualThreadTest {
static List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
// 开启线程 统计平台线程数
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() -> {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
updateMaxThreadNum(threadInfo.length);
}, 10, 10, TimeUnit.MILLISECONDS);
long start = System.currentTimeMillis();
// 虚拟线程
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
// 使用平台线程
// ExecutorService executor = Executors.newFixedThreadPool(200);
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
try {
// 线程睡眠 0.5 s,模拟业务处理
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException ignored) {
}
});
}
executor.close();
System.out.println("max:" + list.get(0) + " platform thread/os thread");
System.out.printf("totalMillis:%dms\n", System.currentTimeMillis() - start);
}
// 更新创建的平台最大线程数
private static void updateMaxThreadNum(int num) {
if (list.isEmpty()) {
list.add(num);
} else {
Integer integer = list.get(0);
if (num > integer) {
list.add(0, num);
}
}
}
}
请求数 10000 单请求耗时 1s:
// Virtual Thread
max:22 platform thread/os thread
totalMillis:1806ms
// Platform Thread 线程数200
max:209 platform thread/os thread
totalMillis:50578ms
// Platform Thread 线程数500
max:509 platform thread/os thread
totalMillis:20254ms
// Platform Thread 线程数1000
max:1009 platform thread/os thread
totalMillis:10214ms
// Platform Thread 线程数2000
max:2009 platform thread/os thread
totalMillis:5358ms
请求数 10000 单请求耗时 0.5s:
// Virtual Thread
max:22 platform thread/os thread
totalMillis:1316ms
// Platform Thread 线程数200
max:209 platform thread/os thread
totalMillis:25619ms
// Platform Thread 线程数500
max:509 platform thread/os thread
totalMillis:10277ms
// Platform Thread 线程数1000
max:1009 platform thread/os thread
totalMillis:5197ms
// Platform Thread 线程数2000
max:2009 platform thread/os thread
totalMillis:2865ms
- 可以看到在密集IO的场景下,需要创建大量的平台线程异步处理才能达到虚拟线程的处理速度。
- 因此,在密集IO的场景,虚拟线程可以大幅提高线程的执行效率,减少线程资源的创建以及上下文切换。
注意:有段时间JDK一直致力于Reactor响应式编程来提高Java性能,但响应式编程难以理解、调试、使用,最终又回到了同步编程,最终虚拟线程诞生。
虚拟线程的底层原理是什么?
虚拟线程是Java 19中的一个预览特性,它是Project Loom的一部分,旨在简化并发编程并提高应用程序的性能。在讨论虚拟线程的底层原理之前,我们先来看看传统的线程。
传统的Java线程与操作系统的线程是一对一映射的。这意味着如果你创建100个Java线程,操作系统层面也会有100个线程。这种线程模型有几个缺点:
- 资源消耗:每个线程都会消耗一定的内存和处理器资源。
- 线程创建和上下文切换开销:创建线程和在线程之间进行上下文切换需要时间和资源。
- 可扩展性限制:由于资源限制,创建大量的线程是不可行的。
虚拟线程的底层原理如下:
- 用户态线程(User-Level Threads):虚拟线程是一种用户态线程,也就是说,它们是由Java运行时管理的,而不是直接映射到操作系统线程。这类似于其他语言中的“绿色线程”或“协程”。
- 轻量级任务调度:虚拟线程的调度是由Java运行时在用户空间内完成的,而不是操作系统。这意味着虚拟线程的创建和上下文切换比操作系统线程要轻量得多。
- 与操作系统线程的映射:虚拟线程在执行实际工作(如执行I/O操作或等待锁)时,会被映射到较少的操作系统线程上。这些操作系统线程通常被称为“载体线程”(Carrier Threads)。这种映射是多对一的,即多个虚拟线程可能会映射到同一个操作系统线程上。
- 栈空间管理:由于虚拟线程是轻量级的,它们的栈空间不需要像操作系统线程那样固定分配一大块内存。虚拟线程的栈空间可以根据需要动态地分配和调整。
- ForkJoinPool:虚拟线程通常在ForkJoinPool中执行,这是一个专门为分治任务设计的线程池。它能够高效地处理大量的任务和线程。
以下是虚拟线程的一些关键特性:
- 可扩展性:可以创建数以百万计的虚拟线程,这对于传统的线程模型来说是不可能的。
- 高效性:虚拟线程的创建和上下文切换开销远小于操作系统线程。
- 兼容性:现有的Java代码可以无缝地使用虚拟线程,无需大规模重构。
需要注意的是,虚拟线程目前仍然是Java的一个预览特性,这意味着在未来的Java版本中它的API和行为可能会发生变化。开发者在使用时应该密切关注相关的更新和变化。