Java 平台自诞生以来,其并发模型主要基于“平台线程”(Platform Threads),即操作系统线程的一对一映射。当并发量达到数万级别时,创建和管理这些重量级的操作系统线程会带来巨大的内存开销和上下文切换成本,严重限制了Java在高并发I/O密集型场景下的性能。
Java 21 正式引入的虚拟线程(Virtual Threads,源自 Project Loom)彻底改变了这一现状。虚拟线程是一种极度轻量级的线程,它由JVM管理,而不是直接映射到操作系统线程。
虚拟线程的核心优势:调度器与载体线程
虚拟线程的关键在于其高效的调度机制。虚拟线程不需要为每个任务创建新的OS线程,而是将数百万个虚拟线程“挂载”到数量有限的载体线程(Carrier Threads,通常是标准的平台线程,默认由一个 ForkJoinPool 管理)上。
当一个虚拟线程执行阻塞操作(如网络I/O、Thread.sleep())时,JVM不会阻塞载体线程。相反,JVM会将虚拟线程从载体线程上“卸载”(unmount),并将其状态保存起来。此时,该载体线程可以立即去执行池中等待的另一个虚拟线程。
这种非阻塞的卸载/挂载机制,使得少量平台线程能够高效地协同管理数百万个并发任务,完美解决了“线程过多”的瓶颈。
实践:使用虚拟线程执行高并发任务
在Java 21中,我们不再需要手动管理线程池配置。最推荐的使用方式是利用 Executors.newVirtualThreadPerTaskExecutor(),它会为提交的每一个任务都创建一个新的虚拟线程。
下面我们通过一个示例来对比虚拟线程在高并发(10万个任务)下的执行效率和资源消耗:
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
public class VirtualThreadConcurrencyDemo {
private static final int NUM_TASKS = 100_000; // 模拟10万个并发任务
private static void runTasks(String type, ExecutorService executor) throws InterruptedException {
Instant start = Instant.now();
System.out.printf("\n--- 启动 %d 个 %s 任务 ---\n", NUM_TASKS, type);
try (executor) {
IntStream.range(0, NUM_TASKS).forEach(i ->
executor.submit(() -> {
// 模拟一个轻微的阻塞/IO操作,这是虚拟线程表现优异的场景
try {
Thread.sleep(Duration.ofMillis(1));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 打印当前执行线程的类型
// System.out.println(Thread.currentThread()); // 观察会发现线程数量巨大,但载体线程数量有限
})
);
}
Instant end = Instant.now();
long duration = Duration.between(start, end).toMillis();
System.out.printf("[%s] 完成 %d 个任务耗时: %d ms.%n", type, NUM_TASKS, duration);
}
public static void main(String[] args) throws InterruptedException {
// 1. 平台线程示例(使用固定线程池,否则10万个OS线程会崩溃或资源耗尽)
// runTasks("平台线程", Executors.newFixedThreadPool(200));
// 2. 虚拟线程示例:为每个任务创建虚拟线程
runTasks("虚拟线程", Executors.newVirtualThreadPerTaskExecutor());
// 3. 另一种创建方式 (使用 Thread.ofVirtual() 替代 Executor)
// runTasks("虚拟线程 (Thread.ofVirtual)", Executors.newCachedThreadPool()); // 只需要关注 Executors.newVirtualThreadPerTaskExecutor() 即可
}
}
运行结果分析
当你运行上述代码并观察资源消耗时,你会发现:
- 平台线程 (如果尝试大量创建): 即使使用线程池,任务调度和切换也会占用大量CPU时间,且JVM的内存占用(尤其是线程栈)会非常高。
- 虚拟线程: 完成10万个任务的时间非常短,且JVM进程的内存占用极低。这是因为虽然有10万个虚拟线程实例,但它们共享的是少数(默认与CPU核数相关的)载体线程。
总结
Java 21 虚拟线程的核心机制是利用底层的 ForkJoinPool 作为调度器,通过非阻塞卸载/挂载技术,实现了用户态的轻量级并发。对于传统的 Java 应用,尤其是那些存在大量 I/O 阻塞的代码(如数据库查询、HTTP调用),迁移到虚拟线程几乎不需要修改业务逻辑,但能够瞬间将应用的最大并发能力从数千提升到数百万,从而实现高吞吐量和低延迟的服务。
汤不热吧