Thread.ofVirtual() 是 JDK 21 创建虚拟线程的推荐方式,支持链式配置、语义清晰,是结构化并发的自然入口,兼顾阻塞式编程习惯与高吞吐能力。

Thread.ofVirtual() 是 JDK 21 中创建虚拟线程(Virtual Thread)的推荐方式,它让开发者能以极低成本启动海量轻量级线程,从而替代传统平台线程(Platform Thread)驱动的“一请求一线程”模型。核心价值不在于写法多新颖,而在于用几乎不变的阻塞式编程习惯,获得接近异步非阻塞的吞吐能力。
为什么不用 new Thread() 或 Executors.newVirtualThreadPerTaskExecutor()?
Thread.ofVirtual() 返回的是 Thread.Builder,支持链式配置(如 name、uncaughtExceptionHandler),比直接 new Thread(virtualRunnable) 更规范;相比 Executors 工厂方法,它不隐式绑定线程池,语义更清晰——你明确在“构建一个虚拟线程”,而非“获取某个执行器”。实际生产中,多数场景仍需配合结构化并发(Structured Concurrency)使用,而 Thread.ofVirtual() 正是 Structured Task Scope 的自然入口。
如何用 Thread.ofVirtual() 启动单个虚拟线程处理请求
以下是最简可行示例,模拟一个 HTTP 请求处理逻辑(含 I/O 阻塞):
Runnable handler = () -> {
String result = blockingDbQuery(); // 如 JDBC 查询(未用异步驱动)
sendResponse(result); // 如同步写入 Socket 输出流
};
Thread vt = Thread.ofVirtual()
.name("req-handler-", 0)
.uncaughtExceptionHandler((t, e) -> log.error("VT crashed", e))
.factory()
.newThread(handler);
vt.start();
关键点:
- 调用 .factory() 才能得到 ThreadFactory,再调用 .newThread() 创建实例 —— 这不是一步到位的构造,而是 builder 模式标准流程
- 虚拟线程默认后台(daemon)为 true,无需手动设置;其栈空间按需分配(KB 级),启动开销约 100ns,远低于平台线程(ms 级)
- 它仍可调用 Thread.sleep()、Object.wait()、BlockingQueue.take()、InputStream.read() 等所有传统阻塞 API,JVM 自动挂起/恢复,不消耗 OS 线程
如何与 StructuredTaskScope 结合实现请求生命周期管理
单个虚拟线程适合简单任务,但真实 Web 请求常涉及子任务协同(如并行查 DB + 调第三方 API)。此时应搭配 StructuredTaskScope,确保超时、异常、取消信号能正确传播:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> dbFuture = scope.fork(() -> blockingDbQuery());
Future<String> apiFuture = scope.fork(() -> blockingApiCall());
scope.join(); // 等待全部完成或任一失败
scope.throwIfFailed(); // 抛出首个异常
String result = merge(dbFuture.resultNow(), apiFuture.resultNow());
sendResponse(result);
}
注意:
- scope 内部 fork 的任务默认由虚拟线程执行(JDK 21+ 默认行为),无需显式指定 Thread.ofVirtual()
- 作用域退出时自动 join 所有子任务,并统一处理异常和中断 —— 这解决了传统线程池中“孤儿线程”和“异常静默”的顽疾
- 若需自定义虚拟线程属性(如命名),可用 scope.fork(Thread.ofVirtual().name(...).factory(), task)
迁移旧架构的关键注意事项
从 Tomcat/Jetty 的“一请求一线程”切换到虚拟线程,并非仅替换 Thread 构造方式:
- 不要复用 ThreadLocal:虚拟线程数量可达百万级,每个都持有 ThreadLocal 副本极易 OOM。改用 ScopedValue(JDK 20+)或显式传参
- 谨慎使用 synchronized 和 Object.wait():虽仍可用,但高竞争下会退化为平台线程调度,建议优先用 ReentrantLock + Condition 或结构化并发原语
- 监控指标要更新:OS 层看到的线程数几乎不变(仍是几十个平台线程承载所有 VT),需依赖 JVM TI 或 JFR 中的 VirtualThreadStart/VirtualThreadEnd 事件观测 VT 行为
- 容器需适配:Spring Boot 3.2+、Tomcat 10.1.12+ 已默认启用虚拟线程支持;若自行构建 Web 容器,需将 Executor 替换为 Executors.newVirtualThreadPerTaskExecutor() 并注入到 Servlet 容器

















