Virtual Thread 是 JVM 用户态轻量级调度抽象,底层复用平台线程,创建/挂起/唤醒不陷入内核;与普通线程本质区别在于调度开销低、默认 daemon、不继承 ThreadLocal、ID 非 OS 级。

Virtual Thread 是什么,和普通 Thread 有啥本质区别
它不是新线程模型,而是 JVM 在用户态做的轻量级调度抽象。底层还是用平台线程(PlatformThread)跑,但 VirtualThread 的创建、挂起、唤醒不进内核,开销接近对象实例化。
常见错误现象:以为开了百万 VirtualThread 就能扛住百万并发请求——其实瓶颈常在数据库连接池、HTTP 客户端复用、或没关闭的 InputStream 上,而非线程本身。
-
VirtualThread默认是 daemon 线程,主线程退出后自动终止,写 demo 时容易“啥都没打印就结束了” - 它不支持
ThreadLocal的默认行为(值不会自动继承),需显式用InheritableThreadLocal或ScopedValue(Java 20+) -
Thread.currentThread()返回的是VirtualThread实例,但thread.getId()是自增 long,不反映 OS 级线程 ID
怎么安全地用 VirtualThread 替换传统线程池
别直接把 Executors.newFixedThreadPool(n) 换成 Executors.newVirtualThreadPerTaskExecutor() 就完事。关键在“任务是否阻塞”和“阻塞是否可中断”。
使用场景:适合大量短生命周期、含 I/O 阻塞(如 HTTP 调用、DB 查询、文件读取)的任务;不适合 CPU 密集型计算(会挤占 carrier 线程,反而降低吞吐)。
立即学习“Java免费学习笔记(深入)”;
- 阻塞调用必须用支持中断的 API:比如用
HttpClient而非老式HttpURLConnection,用CompletableFuture.supplyAsync(..., executor)显式传入虚拟线程执行器 - 不要在线程内手动调用
Thread.sleep()或Object.wait(),它们会让虚拟线程“卡住” carrier,改用Thread.sleep(Duration)(Java 19+ 重载)或ScheduledExecutorService - 日志框架若依赖
ThreadLocal(如 MDC),需确认版本是否适配虚拟线程(Logback 1.4+、SLF4J 2.0+ 支持)
VirtualThread 下的异常传播和监控怎么搞
虚拟线程崩溃时,默认不会打印堆栈到控制台,除非你捕获了 UncaughtExceptionHandler 并显式处理。
常见错误现象:HTTP 接口偶尔 500,日志里却找不到异常——因为异常发生在虚拟线程里,而默认异常处理器只监听平台线程。
-
设置全局处理器:
Thread.setDefaultUncaughtExceptionHandler((t, e) -> { System.err.println("Uncaught in " + t + ": " + e); });注意:这个 handler 对所有线程生效,包括虚拟线程。 JFR(Java Flight Recorder)支持虚拟线程追踪,但需开启额外事件:
-XX:StartFlightRecording=duration=60s,filename=recording.jfr,settings=profile,virtualthreads=truejstack和jcmd默认不显示虚拟线程,要用jcmd <pid> VM.native_memory summary结合 JFR 分析,而不是靠线程 dump 数数量
虚拟线程的“千万级”是理论调度能力,实际压测时最容易被忽略的,是 carrier 线程数配置(-XX:MaxJavaStackTraceDepth 影响不大,但 -XX:ActiveProcessorCount 和 OS 层的 ulimit -u 才真卡脖子)。








