虚拟线程(Virtual Threads)#
版本信息
- 最低 JDK:21(预览 19/20 → 21 正式)
- JEP:JEP 444
为什么需要#
JDK 8 里要写高并发「每个请求一个线程」的代码,平台线程昂贵(~1MB 栈),上千个就吃光内存,于是被迫用异步回调(CompletableFuture)/ 响应式框架,代码可读性骤降。虚拟线程让你用同步阻塞的写法达到异步的吞吐,单 JVM 可轻松承载数十万级虚拟线程(远超平台线程上限;具体取决于堆大小与栈深度)。
语法#
package com.javamodern.vthread;
public class VirtualThreadDemo {
// 创建并启动一个虚拟线程(封装 Thread.ofVirtual,便于演示与测试)
public static Thread startVirtualThread(String name, Runnable task) { // (1)
return Thread.ofVirtual().name(name).start(task);
}
public static void main(String[] args) throws InterruptedException {
Thread vt = startVirtualThread("my-virtual", () ->
System.out.println("running on " + Thread.currentThread()));
vt.join();
System.out.println("isVirtual = " + vt.isVirtual());
}
}
- :material-lightbulb:
startVirtualThread封装Thread.ofVirtual()创建虚拟线程,API 与平台线程一致,学习成本几乎为零。
与 JDK 8 旧写法对比#
Thread t = new Thread(() -> { /* 阻塞 IO */ });
t.start();
Thread vt = Thread.ofVirtual().start(() -> { /* 阻塞 IO */ });
底层原理#
虚拟线程是用户态轻量线程,挂载(mount)到少量载体平台线程(ForkJoinPool,数量默认等于可用 CPU 核数)上运行,阻塞时被卸载(unmount),载体线程立即去跑别的虚拟线程(M:N 调度):
flowchart TD
A["Thread.ofVirtual().start(task)"] --> B["挂载到载体线程 ForkJoinPool"]
B --> C{"task 阻塞?"}
C -- 否 --> D["运行至结束"]
C -- 是 --> E["卸载 unmount<br/>载体线程立即释放"]
E --> F["阻塞条件就绪"]
F --> B
style A fill:#bbdefb
style E fill:#ffe0b2
常见坑 / 最佳实践#
- 不要池化虚拟线程:用完即弃,用
Executors.newVirtualThreadPerTaskExecutor()提交。 - synchronized 钉住(pinning)载体线程:JDK 21 中
synchronized内阻塞会钉住载体,应用ReentrantLock替代。 - 虚拟线程适合 IO 密集,不适合 CPU 密集(不增加 CPU 核数)。
小结#
虚拟线程让「同步写法 + 高吞吐」兼得,是 JDK 8→21 并发模型最大的一次跃迁。