
如果说现代 Java 有一件等了二十年的事那就是廉价的大并发。从 JDK 1.0 起Java 的线程就直接映射操作系统线程1:1 模型一个线程要占 1MB 栈、创建贵、调度重高并发只能靠线程池省着用。JDK 19 预览的**虚拟线程Virtual Threads**打破了这个天花板——它是 Project Loom 的结晶让百万级并发变得像写同步代码一样简单。这一篇讲虚拟线程是什么、为什么快、预览期怎么用以及它如何改变你写并发的方式。一、为什么传统线程不够用操作系统线程平台线程的两个硬伤贵默认栈 1MB百万线程就是 1TB 内存根本扛不住。阻塞即浪费一旦Thread.sleep、数据库查询、网络 IO 阻塞这个 OS 线程就被挂起CPU 干等吞吐量被卡在线程数上。所以长久以来的解法都是线程池 异步回调用少量平台线程跑很多任务用CompletableFuture、响应式Reactor/RxJava避免阻塞。但异步回调把代码切成意大利面条可读性和调试都惨。虚拟线程的逻辑正好反过来让每个任务一个线程重新变便宜于是你又能写直白的同步代码又不牺牲吞吐。二、虚拟线程是什么虚拟线程是JDK 管理的轻量级线程不绑定固定 OS 线程由 JVM 调度挂在少量载体线程carrier即平台线程上运行。遇到阻塞IO、BlockingQueue.take、锁等待等挂载点虚拟线程从载体上卸载载体去跑别的虚拟线程阻塞结束再挂载回来。内存占用极小初始几 KB按需增长创建成本接近零可以大量创建百万级。一句话虚拟线程 便宜到可以一个请求一个线程的并发单元且阻塞不浪费载体。三、怎么用预览期写法JDK 19 预览需--enable-preview// 创建并启动一个虚拟线程ThreadvtThread.ofVirtual().name(task-,0).start(()-{System.out.println(跑在虚拟线程上Thread.currentThread());});vt.join();// 大量并发每个任务一个虚拟线程毫无压力try(varexecutorExecutors.newVirtualThreadPerTaskExecutor()){for(inti0;i1_000_000;i){executor.submit(()-{Thread.sleep(1000);// 阻塞没问题载体去跑别的returndoWork();});}}// 关闭即等待全部完成注意Executors.newVirtualThreadPerTaskExecutor()它不为每任务真开 OS 线程而是为每个任务起一个虚拟线程。对比传统newFixedThreadPool(200)只能 200 并发这个执行器能轻松百万任务并发且代码还是同步写法sleep、顺序调用没有回调地狱。四、为什么虚拟线程能既便宜又不阻塞关键在于JVM 识别挂载点pin 点。当虚拟线程执行到会阻塞的操作大多数是 JDK 内部的 IO、锁、Object.waitJVM 自动把它从载体线程卸载阻塞解除再调度回任意空闲载体。但要注意一个坑——synchronized 会钉住pin虚拟线程在synchronized块内阻塞时虚拟线程暂时无法卸载会占着载体退化成近似平台线程。所以虚拟线程的最佳拍档是java.util.concurrent.locks.ReentrantLock它配合虚拟线程能正确卸载privatefinalReentrantLocklocknewReentrantLock();voidwork(){lock.lock();// 虚拟线程在此阻塞可正常卸载try{/* 临界区 */}finally{lock.unlock();}}经验用虚拟线程时把热路径上的synchronized换成ReentrantLock避免 pin 影响吞吐。五、虚拟线程不是银弹必须清醒认识边界CPU 密集型任务不受益虚拟线程优化的是等待不是计算。纯计算任务多了虚拟线程只是多了调度开销该用线程池算还是算。不要池化虚拟线程虚拟线程本就极廉价再套线程池是画蛇添足直接用newVirtualThreadPerTaskExecutor。thread-local 语义变了海量虚拟线程下thread-local 会变成内存负担JDK 21 引入作用域值ScopedValue作为替代。调试视角变了百万虚拟线程传统线程 dump 会爆需用 JDK 新的工具jcmd的虚拟线程视图。六、顺带JDK 18/19 其他特性版本特性说明18UTF-8 默认字符集跨平台不再因默认编码不一致翻车18简单 Web 服务器jwebserver临时静态服务零依赖18代码片段 APIJEP 413文档里嵌入可执行代码19结构化并发预览把一组相关任务当一个单元管理见下19外部函数与内存 API预览Panama 项目替代 JNI 调本地代码19Vector API孵化SIMD 向量计算加速**结构化并发Structured Concurrency**和虚拟线程是搭档它用StructuredTaskScope把父任务派生的子任务绑成树父取消/异常时子任务自动取消避免线程泄漏、异常吞噬——让并发代码像try-with-resources一样有边界。try(varscopenewStructuredTaskScope.ShutdownOnFailure()){FutureStringuscope.fork(()-fetchUser());FutureIntegeroscope.fork(()-fetchOrder());scope.join();// 等所有子任务scope.throwIfFailed();// 任一失败则整体失败returnnewResult(u.resultNow(),o.resultNow());}七、本篇记住的清单虚拟线程是 JVM 管理的轻量级线程遇到阻塞自动从载体卸载便宜到可一任务一线程。用法Thread.ofVirtual().start(...)或Executors.newVirtualThreadPerTaskExecutor()预览期需--enable-preview。虚拟线程优化等待而非计算CPU 密集任务不受益且不要池化。synchronized会 pin 虚拟线程热路径换ReentrantLock。thread-local 在海量虚拟线程下是负担JDK 21 用 ScopedValue 替代。结构化并发StructuredTaskScope把子任务绑成树取消/异常自动传播。18/19 还带来 UTF-8 默认编码、外部函数内存 APIPanama、Vector API 等。虚拟线程预览只是序幕。 JDK 21它正式转正加上分代 ZGC 和一系列增强成为并发写法分水岭的 LTS。