虚拟线程必须显式创建,如thread.ofvirtual().unstarted(runnable),不能用new thread()或非virtual线程池;阻塞调用需jdk原生支持,cpu密集型任务应避免使用。

虚拟线程必须用 Thread.ofVirtual() 创建,不能 new Thread() 或 Executors.newVirtualThreadPerTaskExecutor() 以外的线程池
Java 21 的虚拟线程不是靠「开启某个开关」就能自动启用的,它必须显式创建。直接 new Thread(Runnable) 创建的是平台线程;用 Thread.startVirtualThread(Runnable) 或更推荐的 Thread.ofVirtual().unstarted(Runnable) 才是虚拟线程。
常见错误:把 Executors.newFixedThreadPool(10) 拿来跑大量 I/O 任务,以为加了虚拟线程就自动优化——其实完全没生效,仍是 10 个 OS 线程卡死在阻塞调用上。
-
Thread.ofVirtual().start(Runnable):适合单次、短生命周期任务 -
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();:适合任务量大、需统一管理生命周期的场景(注意:该 executor 默认不关闭,必须手动close()) - 不要混用:虚拟线程里再
new Thread(...).start(),会退化为平台线程,失去轻量优势
阻塞调用(如 Thread.sleep()、Object.wait()、文件/网络 I/O)天然挂起虚拟线程,但需确保底层 API 支持 JDK 调度
虚拟线程的“自动挂起”只对 JDK 内建的阻塞点有效:比如 Thread.sleep()、LockSupport.park()、NIO 的 AsynchronousFileChannel、以及基于 NIO 的 HttpClient(非 HttpURLConnection)。传统 BIO 阻塞(如 FileInputStream.read()、Socket.getInputStream().read())仍会阻塞整个载体平台线程。
典型陷阱:用 new URL("https://...").openStream() 发起 HTTP 请求——这是阻塞式 BIO,会拖垮整个虚拟线程调度器。必须改用 HttpClient.newBuilder().build().sendAsync(...)。
立即学习“Java免费学习笔记(深入)”;
- 优先使用 JDK 21+ 原生异步 API(如
HttpClient、AsynchronousSocketChannel) - 第三方库(如 OkHttp、Netty)需确认是否已适配虚拟线程(OkHttp 4.12+ 开启
Dispatcher的 virtual thread mode) - 数据库连接需搭配支持虚拟线程的驱动(如 PostgreSQL 42.6.0+ +
connectTimeout=0参数)
Thread.currentThread().isVirtual() 是唯一可靠的运行时判断方式,别信 IDE 提示或线程名
IDE(如 IntelliJ)可能把虚拟线程显示为 “VirtualThread[#xx]”,但这只是调试信息,不可用于逻辑分支。有些框架(如 Spring Boot 3.2+)默认启用虚拟线程支持,但若你没配置 spring.threads.virtual.enabled=true,实际跑的仍是平台线程——此时 Thread.currentThread().getName() 可能含 “virtual”,但 isVirtual() 返回 false。
真实验证方式只有一条:
if (Thread.currentThread().isVirtual()) {
System.out.println("✅ 在虚拟线程中");
} else {
System.out.println("❌ 实际是平台线程");
}
- 不要依赖日志中线程名是否含 “virtual” 或 “VThread”
- Spring WebMvc 默认不启用虚拟线程,需显式配置
@Bean替换WebServerFactoryCustomizer - JVM 启动参数
--enable-preview必须存在(Java 21 虚拟线程仍是预览特性),否则Thread.ofVirtual()直接抛UnsupportedOperationException
虚拟线程不是银弹:高 CPU 密集型任务反而更慢,且无法用 ThreadLocal 存储上下文
虚拟线程本质是“快速创建 + 快速挂起”的协作式调度单元,它的价值在 I/O 等待密集型场景(如每请求一次 DB + 两次 HTTP 调用)。一旦进入纯计算(如加密解密、图像处理、复杂循环),频繁调度开销 + 缺少 CPU 亲和性,性能通常比平台线程差 10%–30%。
另一个关键限制:ThreadLocal 对虚拟线程无效——因为虚拟线程会在不同平台线程间迁移,ThreadLocal 的值不会跟随迁移。Spring 的 RequestContextHolder、MDC 日志上下文等默认基于 ThreadLocal,必须改用 ScopedValue(Java 21 新 API)或框架提供的虚拟线程安全替代方案(如 Spring 的 @Scope("request") 在 WebFlux 下才真正隔离)。
- CPU 密集任务应继续用
ForkJoinPool.commonPool()或固定大小平台线程池 - 需要上下文透传时,用
ScopedValue.where(SCOPE_KEY, value).run(() -> {...})替代ThreadLocal.set() - 监控虚拟线程状态需用 JFR(JDK Flight Recorder)事件
jdk.VirtualThreadStart/jdk.VirtualThreadEnd,普通线程 dump 看不到它们










