☰
07-JDK 18/19 虚拟线程来袭:并发编程要变天了
2026/10/30 2:19:17 网站建设 项目流程

如果说现代 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:

// 创建并启动一个虚拟线程Threadvt=Thread.ofVirtual().name("task-",0).start(()->{System.out.println("跑在虚拟线程上:"+Thread.currentThread());});vt.join();// 大量并发:每个任务一个虚拟线程,毫无压力try(varexecutor=Executors.newVirtualThreadPerTaskExecutor()){for(inti=0;i<1_000_000;i++){executor.submit(()->{Thread.sleep(1000);// 阻塞?没问题,载体去跑别的returndoWork();});}}// 关闭即等待全部完成

注意Executors.newVirtualThreadPerTaskExecutor():它不为每任务真开 OS 线程,而是为每个任务起一个虚拟线程。对比传统newFixedThreadPool(200)只能 200 并发,这个执行器能轻松百万任务并发,且代码还是同步写法(sleep、顺序调用),没有回调地狱。


四、为什么虚拟线程能"既便宜又不阻塞"

关键在于JVM 识别挂载点(pin 点)。当虚拟线程执行到会阻塞的操作(大多数是 JDK 内部的 IO、锁、Object.wait),JVM 自动把它从载体线程卸载;阻塞解除再调度回任意空闲载体。

但要注意一个坑——synchronized 会"钉住"(pin)虚拟线程:在synchronized块内阻塞时,虚拟线程暂时无法卸载,会占着载体,退化成近似平台线程。所以虚拟线程的最佳拍档是java.util.concurrent.locks.ReentrantLock(它配合虚拟线程能正确卸载):

privatefinalReentrantLocklock=newReentrantLock();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代码片段 API(JEP 413)文档里嵌入可执行代码
19结构化并发(预览)把一组相关任务当"一个单元"管理(见下)
19外部函数与内存 API(预览)Panama 项目,替代 JNI 调本地代码
19Vector API(孵化)SIMD 向量计算加速

**结构化并发(Structured Concurrency)**和虚拟线程是搭档:它用StructuredTaskScope把"父任务派生的子任务"绑成树,父取消/异常时子任务自动取消,避免线程泄漏、异常吞噬——让并发代码像"try-with-resources"一样有边界。

try(varscope=newStructuredTaskScope.ShutdownOnFailure()){Future<String>u=scope.fork(()->fetchUser());Future<Integer>o=scope.fork(()->fetchOrder());scope.join();// 等所有子任务scope.throwIfFailed();// 任一失败则整体失败returnnewResult(u.resultNow(),o.resultNow());}

七、本篇记住的清单

  1. 虚拟线程是 JVM 管理的轻量级线程,遇到阻塞自动从载体卸载,便宜到可"一任务一线程"。
  2. 用法:Thread.ofVirtual().start(...)或Executors.newVirtualThreadPerTaskExecutor(),预览期需--enable-preview。
  3. 虚拟线程优化"等待"而非"计算",CPU 密集任务不受益,且不要池化。
  4. synchronized会 pin 虚拟线程,热路径换ReentrantLock。
  5. thread-local 在海量虚拟线程下是负担,JDK 21 用 ScopedValue 替代。
  6. 结构化并发StructuredTaskScope把子任务绑成树,取消/异常自动传播。
  7. 18/19 还带来 UTF-8 默认编码、外部函数内存 API(Panama)、Vector API 等。

虚拟线程预览只是序幕。 JDK 21它正式转正,加上分代 ZGC 和一系列增强,成为"并发写法分水岭"的 LTS。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询