当前位置: 首页 > news >正文

10000 个并发任务,虚拟线程 1 秒 vs 传统线程池 50 秒,差距在哪?

大家好,我是Java1234_小锋老师。

同样是让 10000 个任务各等待 1 秒,固定大小为 200 的传统线程池大约要 50 秒,虚拟线程却可能只用 1~2 秒。它并不是把 CPU 变快了,而是让“等待”不再白白占着宝贵的平台线程。

先说结论

这个标题里的“1 秒”和“50 秒”,有一个很重要的前提:任务大部分时间都在等待,例如等待数据库、远程接口、文件或者消息返回。

如果 10000 个任务都要等待 1 秒,传统线程池只有 200 个线程,那么它只能分 50 批处理:

10000 ÷ 200 × 1 秒 = 50 秒

虚拟线程则可以近似做到“一个任务一个线程”。当某个任务开始等待时,它会暂时让出底层平台线程,让平台线程继续执行别的任务。因此,10000 个等待可以大量重叠,总耗时会接近单次等待时间,而不是把每一批的时间累加起来。

不过,这不是一条通用性能定律。真实耗时会受到机器配置、JDK 版本、数据库连接池和下游接口承载能力等因素影响。

为什么会出现 1 秒和 50 秒的差距

传统 Java 线程通常会对应到操作系统线程。操作系统线程并不轻:创建它需要内存,切换它也有成本,所以项目里一般会使用固定大小的线程池。

线程池能控制资源,却也形成了“窗口数量”的限制。200 个窗口同时只能接待 200 个任务,剩下的任务只能排队。

虚拟线程由 JVM 管理,比平台线程轻得多。我们可以创建成千上万个虚拟线程,再由少量平台线程负责真正执行。它更像是给每位顾客发一个号码牌:顾客等待资料时,窗口可以先服务其他人,不必陪着一起等。

固定线程池 200

虚拟线程

10000 个任务到达

使用哪种线程

200 个执行,其余排队

约 50 批完成

每个任务拥有虚拟线程

I/O 等待时让出平台线程

大量等待时间相互重叠

用一段代码实际对比

下面的示例需要JDK 21 或更高版本Thread.sleep(1000)用来模拟一次耗时 1 秒的网络或数据库请求。

importjava.time.Duration;importjava.time.Instant;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;/** * 传统线程池与虚拟线程吞吐量对比示例。 */publicclassVirtualThreadBenchmark{privatestaticfinalintTASK_COUNT=10_000;/** * 程序入口:分别执行两种线程模型。 */publicstaticvoidmain(String[]args)throwsInterruptedException{run("传统固定线程池",Executors.newFixedThreadPool(200));run("虚拟线程",Executors.newVirtualThreadPerTaskExecutor());}/** * 提交任务并统计全部任务完成所需的时间。 * * @param name 测试名称 * @param executor 待测试的执行器 */privatestaticvoidrun(Stringname,ExecutorServiceexecutor)throwsInterruptedException{Instantstart=Instant.now();try(executor){for(inti=0;i<TASK_COUNT;i++){executor.submit(()->{try{// 模拟等待远程接口、数据库或文件返回Thread.sleep(1_000);}catch(InterruptedExceptionexception){// 恢复中断标记,交给上层决定如何处理Thread.currentThread().interrupt();}});}executor.shutdown();executor.awaitTermination(2,TimeUnit.MINUTES);}longelapsed=Duration.between(start,Instant.now()).toMillis();System.out.printf("%s:%.2f 秒%n",name,elapsed/1_000.0);}}

一次典型的测试结果可能是:

传统固定线程池:50.18 秒 虚拟线程:1.12 秒

这里快的不是sleep本身,而是 10000 次等待几乎同时发生了。若把任务换成复杂计算,例如压缩视频或计算哈希,虚拟线程通常不会带来这种数量级的提升,因为 CPU 依然只有那么多核心。

虚拟线程到底做了什么

可以把平台线程看作真正干活的“工人”,虚拟线程则是轻量的“任务单”。

当虚拟线程执行普通 Java 代码时,JVM 会把它挂载到某个平台线程上。遇到支持虚拟线程的阻塞操作后,虚拟线程会被卸载,平台线程转身处理其他任务。等数据返回,原来的虚拟线程再找合适的平台线程继续运行。

整个过程对业务代码很友好:我们仍然可以按照从上到下的同步写法编程,不需要为了提高并发量,把代码拆成许多回调。

适合放到哪些业务里

虚拟线程最适合“请求很多、等待很多、计算不重”的场景,例如:

  1. 调用多个远程接口:订单详情需要同时查询商品、库存、优惠和物流。
  2. 数据库访问:大量请求都在等待 SQL 返回,但要注意连接池仍然有容量上限。
  3. 批量文件处理:读取大量小文件,或者上传、下载对象存储中的文件。
  4. 消息消费和网关服务:每个任务逻辑不复杂,却经常等待网络响应。

例如,同时查询三个远程服务时,可以直接为每个查询创建一个虚拟线程:

importjava.util.concurrent.Executors;importjava.util.concurrent.Future;/** * 使用虚拟线程并行查询订单相关信息。 */publicclassOrderDetailService{/** * 并行获取订单、库存和物流信息。 * * @return 汇总后的订单详情 */publicStringloadOrderDetail()throwsException{try(varexecutor=Executors.newVirtualThreadPerTaskExecutor()){Future<String>order=executor.submit(()->queryOrder());Future<String>stock=executor.submit(()->queryStock());Future<String>delivery=executor.submit(()->queryDelivery());returnorder.get()+","+stock.get()+","+delivery.get();}}/** 查询订单信息。 */privateStringqueryOrder()throwsInterruptedException{Thread.sleep(300);return"订单信息";}/** 查询库存信息。 */privateStringqueryStock()throwsInterruptedException{Thread.sleep(400);return"库存信息";}/** 查询物流信息。 */privateStringqueryDelivery()throwsInterruptedException{Thread.sleep(500);return"物流信息";}}

三个查询顺序执行大约需要 1.2 秒,并行后则接近最慢的那一次,也就是 0.5 秒。

虚拟线程也不是万能药

使用前还要留意几件事:

  • 它不能突破下游限制。数据库连接池只有 50 个连接,创建 10000 个虚拟线程也不会变出更多连接。
  • CPU 密集任务不会凭空变快。计算量没减少,过多并发反而可能增加调度开销。
  • 不要把虚拟线程池化。虚拟线程本身很轻,通常直接按任务创建;需要限制并发时,应使用信号量等方式保护下游资源。
  • 压测要贴近真实业务。sleep只是帮助理解原理,生产环境还要观察超时、内存、连接数和错误率。

最后总结

“虚拟线程 1 秒,传统线程池 50 秒”的核心,不是虚拟线程拥有更强的计算能力,而是它大幅降低了线程的使用成本,让大量 I/O 等待可以同时发生。

对于高并发、I/O 密集型 Java 服务,虚拟线程让我们既能保留清晰的同步代码,又能获得接近异步编程的吞吐能力。升级到 JDK 21 后,它很值得放进真实接口和压测环境里试一试——但别只盯着线程数量,也要一起检查数据库、连接池和下游服务能不能接得住。

http://www.jsqmd.com/news/1399705/

相关文章:

  • 玻璃钢源头智造赋能基建防护材料升级观察 - 天下观知
  • Catppuccin Cursors图标设计原理:如何用SVG实现跨桌面环境的一致性体验
  • 东莞彩标印刷厂在哪里?彩标彩卡彩盒等印刷采购避坑指南 - 变量人生001
  • 广州江诗丹顿纵横四海4500V回收,持证钟表鉴定师无损检测,读懂行情再出手不踩坑,国家钟表质检认证 - 资讯快报员
  • 从 Babel 迁移到 optimize-plugin:完整步骤与常见问题解答
  • 如何从零开始学习SwiftUI动画?kavsoft-swiftui-animations项目入门教程
  • 如何用 docker-minecraft-server 从零搭建 Minecraft 服务器:从首次启动到整合包部署的完整指南
  • 纪元轮回:维性力网与文明沉沦之谜044
  • 2026上海静安钻石变现全攻略|从4C评估到当场回款,正规渠道一次讲清 - 二奢行情观察局
  • SQLAdvisor 实战指南:输入一条SQL,自动拿到索引优化建议
  • 南京2026奢侈品包包回收商户|本地卖包行情参考 - 品牌观测员
  • 老电脑也能流畅开 20 个标签页:一份 Thorium 浏览器性能优化体验指南
  • 体验 AI 操控本机,OpenClaw 小龙虾 Win10 配置全解(含安装包)
  • 东莞说明书印刷厂家怎么选?十年采购拆解彩盒彩卡色差避坑指南 - 变量人生001
  • Asmble未来路线图:链接器、脚本支持与多语言集成展望
  • VValidator条件验证教程:灵活处理复杂表单逻辑的实用指南
  • md2wechat-skill 与主流 AI 工具集成教程:Claude、Kimi 等 Agent 调用方法
  • Programming Resources项目深度解析:程序员必备的一站式学习宝库
  • macOS 如何把任何文档“打印“成 PDF?RWTS-PDFwriter 免费虚拟打印机完整上手指南
  • GP2040-CE固件从零上手:30分钟用Raspberry Pi Pico打造多平台游戏手柄
  • 2026杭州成考机构怎么选?从办学资质到服务体系的选择框架 - U渠道
  • 佛山黄金回收商家不会主动告诉你的秘密,看懂避开绝大多数坑 - 路人老杨实测
  • notepad-- 代码折叠怎么用?7个技巧让万行代码浏览效率翻倍
  • GodotFirebase常见错误解决手册:从认证失败到数据同步的15个问题修复
  • Daedalus未来路线图:新特性预告与N64游戏模拟技术演进趋势
  • OpenClaw 小白教程|零基础一键搭建本地 AI 自动化智能体(含安装包)
  • 广州包包 - 资讯快报员
  • ml-projects性能优化指南:让你的Web ML模型运行速度提升300%
  • 沃尔玛购物卡回收|闲置商超卡盘活实用思路分享 - 团团收购物卡回收
  • 如何快速上手cpuminer-multi?零基础入门到挖矿实战的完整指南