虚拟线程是JVM用户态调度抽象,不绑定OS线程,支持百万级并发但禁阻塞和CPU密集;平台线程直映OS线程,栈大且受限;需用Thread.ofVirtual()或newVirtualThreadPerTaskExecutor()创建,禁用join()和传统阻塞IO。

Java虚拟线程不是“新线程实现”,而是JVM在用户态做的调度抽象——它不绑定OS线程,所以能轻松创建百万级并发任务,但代价是不能长期阻塞、不适用CPU密集型场景。
虚拟线程和普通线程到底差在哪
关键区别不在API,而在调度模型:Thread.start() 创建的平台线程(Platform Thread)直接映射到OS线程,受系统线程数限制;而 Thread.ofVirtual().start() 创建的虚拟线程由JVM在少量平台线程上协作式调度,类似协程。
- 平台线程:每个线程占用1MB栈空间,默认栈大小不可动态调整,
OutOfMemoryError: unable to create new native thread很常见 - 虚拟线程:默认栈仅几KB,按需增长,生命周期由JVM管理,退出后资源自动回收
- 不能混用阻塞调用:比如在虚拟线程里调用
Thread.sleep(1000)会挂起整个载体平台线程,导致其他虚拟线程“假死”
怎么安全地启动和使用虚拟线程
必须通过 Thread.Builder 或 Executors.newVirtualThreadPerTaskExecutor() 启动,直接 new Thread(Runnable) 仍创建平台线程。
- 推荐方式:
Thread.ofVirtual().unstarted(runnable).start()—— 显式、可控、语义清晰 - 批量任务首选:
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); executor.submit(runnable); - 避免手动
join():虚拟线程不支持Thread.join()阻塞等待,应改用CompletableFuture或结构化并发(如StructuredTaskScope) - 调试时注意:
jstack默认不显示虚拟线程,需加-XX:+UnlockDiagnosticVMOptions -XX:+PrintConcurrentLocks或用 JFR 录制
哪些代码会悄悄让虚拟线程“变重”
虚拟线程一旦执行阻塞IO或同步原语,JVM会将其“挂起并移交”给平台线程池处理,失去轻量优势,甚至引发线程饥饿。
立即学习“Java免费学习笔记(深入)”;
- 危险操作:
Object.wait()、synchronized块内耗时过长、Thread.sleep()、传统java.io流读写(非NIO) - 安全替代:
java.nio.channels.AsynchronousFileChannel、HttpClient的异步API、ReentrantLock.lockInterruptibly()+ 超时控制 - 日志陷阱:SLF4J + Logback 默认使用同步Appender,高并发下会成为瓶颈,建议切换为
AsyncAppender或 Log4j2 的无锁异步模式
真正难的不是写对第一行 Thread.ofVirtual(),而是识别出所有隐式阻塞点——尤其在复用老代码、中间件或三方SDK时,它们往往没为虚拟线程做过适配。










