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

孤舟笔记 并发篇二十四 线程池如何知道一个线程的任务已经执行完成?三种方式各有乾坤

文章目录

    • 一、先说结论:感知任务完成的三种方式
    • 二、方式一:Future.get()
    • 三、方式二:CompletionService
    • 四、方式三:CountDownLatch
    • 五、方式四:FutureTask + 回调
    • 六、对比总结
    • 感知任务完成方式全景
    • 回答技巧与点评
        • 标准回答
        • 加分回答
        • 面试官点评

个人网站

你提交了一堆任务到线程池,想等它们全部完成再往下走,怎么搞?或者你想知道某个任务执行完了没有,拿没拿到结果?这些场景都涉及"线程池怎么感知任务完成"。

面试官问这个问题,不只是考 API,更是考你对 Future、CompletionService 这些工具的理解深度。

一、先说结论:感知任务完成的三种方式

方式适用场景特点
Future.get()等待单个任务完成阻塞等待,可获取结果
CompletionService批量任务,按完成顺序获取先完成的先拿到
CountDownLatch批量任务,等待全部完成不关心结果,只等"全部做完"
invokeAll()批量任务,等待全部完成返回所有 Future
isTerminated()线程池关闭后判断需先 shutdown()

一句话记住:单个任务用 Future,批量按序用 CompletionService,批量等完用 CountDownLatch——工具选对,事半功倍。

二、方式一:Future.get()

最基本的方式——提交任务时拿到 Future,调用 get() 阻塞等待:

ExecutorServicepool=Executors.newFixedThreadPool(3);Future<String>future=pool.submit(()->{Thread.sleep(2000);return"任务结果";});// 阻塞等待,直到任务完成Stringresult=future.get();// 👈 最多等多久?一直等!System.out.println(result);// 带超时的等待Stringresult2=future.get(5,TimeUnit.SECONDS);// 👈 5秒超时

问题:如果提交了多个任务,逐个 get() 是串行等待的:

List<Future<String>>futures=newArrayList<>();for(inti=0;i<5;i++){futures.add(pool.submit(()->doTask()));}// ❌ 问题:如果第一个任务最慢,后面快的也得等for(Future<String>f:futures){System.out.println(f.get());// 串行阻塞 👈}

生活类比:你去医院取五个检查报告,挨个窗口等——哪怕第二个报告早出了,你也得先等第一个。

三、方式二:CompletionService

CompletionService 解决了"串行等待"的问题——谁先完成,谁先拿到

ExecutorServicepool=Executors.newFixedThreadPool(3);CompletionService<String>cs=newExecutorCompletionService<>(pool);// 提交 5 个任务for(inti=0;i<5;i++){finalintidx=i;cs.submit(()->doTask(idx));}// 按完成顺序获取结果for(inti=0;i<5;i++){Future<String>f=cs.take();// 👈 取一个已完成的 Future(阻塞)System.out.println(f.get());// 立刻获取结果(不阻塞)}

原理:CompletionService 内部维护了一个BlockingQueue<Future>,任务完成后 Future 自动入队。take() 从队列取,谁先完成谁先进队列

生活类比:五个人同时做饭,谁先做好谁先端上来——不用等最慢的那道菜。

经典应用:从多个搜索引擎取结果,谁先返回就用谁:

CompletionService<String>cs=newExecutorCompletionService<>(pool);cs.submit(()->searchBaidu(keyword));cs.submit(()->searchGoogle(keyword));cs.submit(()->searchBing(keyword));// 谁先返回就用谁Stringresult=cs.take().get();

四、方式三:CountDownLatch

如果你不关心结果,只关心"全部做完",用 CountDownLatch:

inttaskCount=10;CountDownLatchlatch=newCountDownLatch(taskCount);for(inti=0;i<taskCount;i++){pool.submit(()->{try{doTask();}finally{latch.countDown();// 👈 每完成一个,计数器 -1}});}latch.await();// 👈 阻塞等待,直到计数器归零System.out.println("所有任务执行完毕!");

生活类比:10 个工人在干活,你在大门口数人头——最后一个回来了,关门下班。

vs invokeAll():

// invokeAll 也能等全部完成List<Future<String>>futures=pool.invokeAll(tasks);// 👈 阻塞等所有任务完成for(Future<String>f:futures){System.out.println(f.get());}

区别:invokeAll 返回所有 Future,可以拿结果;CountDownLatch 更轻量,只做同步。

五、方式四:FutureTask + 回调

Java 8+ 可以用 CompletableFuture 实现回调式通知:

CompletableFuture.supplyAsync(()->doTask(),pool).thenAccept(result->{System.out.println("任务完成,结果: "+result);// 👈 回调,不阻塞});// 主线程继续做其他事...

优势:不阻塞等待,任务完成后自动回调。这是最现代的方式。

六、对比总结

方式阻塞?获取结果?批量按序?
Future.get()阻塞❌ 串行等
CompletionService阻塞✅ 先完成先取
CountDownLatch阻塞等全部完成
invokeAll()阻塞等全部完成
CompletableFuture非阻塞回调✅ 任意组合

感知任务完成方式全景

感知任务完成 全景 单个任务 └── Future.get() ── 阻塞等待结果 批量任务 ├── CompletionService ── 按完成顺序取(先完先得) ├── CountDownLatch ── 只等全部完成(不管结果) ├── invokeAll() ── 等全部完成(返回所有 Future) └── CompletableFuture ── 回调式,非阻塞 选择策略 ├── 要结果、单个任务 → Future.get() ├── 要结果、批量按序 → CompletionService ├── 不要结果、只等完成 → CountDownLatch └── 不想阻塞 → CompletableFuture 口诀:Future 等单个,Completion 按序取, Latch 等全部,Completable 回调走, 阻塞非阻塞各有所长,场景选对是关键。

回答技巧与点评

标准回答

线程池感知任务完成有几种方式:一是通过 Future.get() 阻塞等待单个任务完成并获取结果;二是用 CompletionService 批量提交任务,按完成顺序获取结果(先完成先拿到);三是用 CountDownLatch 等待所有任务完成(不关心结果);四是用 CompletableFuture 实现非阻塞的回调式通知。其中 CompletionService 解决了 Future.get() 串行等待的问题,CompletableFuture 是最现代的异步编程方式。

加分回答
  1. CompletionService 的原理:它内部维护了一个 BlockingQueue<Future>,把 Executor 和 BlockingQueue 组合在一起。任务完成后,ExecutorCompletionService 会把 Future 封装成 QueueingFuture(覆盖了 done() 方法),任务完成时自动入队。这是生产者-消费者模式的精巧应用
  2. Future.get() 的超时陷阱:get(timeout) 超时后只是抛 TimeoutException,任务仍在后台执行。如果需要真正取消任务,应该 catch 超时后调用 future.cancel(true)
  3. CompletableFuture 的优势:它不仅支持 thenAccept 回调,还支持 thenCombine(合并两个结果)、thenCompose(链式异步)、allOf(等全部)、anyOf(任一完成)等丰富的组合操作,是 Java 异步编程的首选
面试官点评

这道题考的是你对并发工具链的全面掌握。能说出 Future 和 CountDownLatch 是基本要求,能讲清 CompletionService "按完成顺序获取"的原理、以及 CompletableFuture 的回调优势,才说明你有过批量任务处理的实战经验。面试官最想听到的是:你不仅知道有哪些方式,还能根据场景做出正确的选择。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

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

相关文章:

  • 为内部知识问答系统接入 Taotoken 实现智能回复的架构思路
  • # 11|装饰器、闭包与 lambda 表达式
  • 基于MCP协议构建智能购物代理:连接AI与电商平台的实战指南
  • 2026届最火的降AI率网站推荐榜单
  • Kubeflow Trainer:Kubernetes原生分布式AI训练平台实战指南
  • 环境配置与基础教程:涨点从数据源头开始:Albumentations 进阶数据增强管线配置(MixUp、Mosaic、Copy-Paste)
  • 如何轻松下载B站无水印视频?5分钟掌握BiliDownload完整教程
  • 别再只看FLOPs了!ShuffleNetV2作者亲授的4条高效CNN设计实战守则
  • 环境配置与基础教程:告别炼丹玄学:集成 Ray Tune 实现 YOLOv11 超参数自动化搜索与贝叶斯优化
  • LLM个性化评估技术:方法与实战解析
  • ChineseSubFinder终极指南:5分钟搭建你的自动化中文字幕系统
  • MA-EgoQA:多智能体第一视角视频问答基准解析
  • 跨视角物体对应学习:循环一致掩码预测框架解析
  • 国家自然科学基金申请书的LaTeX终极排版方案:5分钟搞定专业格式
  • WinClaw安全实战 17|WinClaw技能发布实战:从本地开发到ClawHub全球分发(含三种发布方式+完整案例)
  • 【Backend Flow工程实践 16】从 Scan Chain 到 Placement:测试结构为什么会影响后端布局?
  • 终极指南:三步让老旧Mac焕发新生,突破系统限制免费升级最新macOS
  • AI智能体容器化部署:基于Docker与Docker Compose的标准化工作空间实践
  • Navicat密码解密终极指南:3分钟快速找回丢失的数据库连接密码
  • 洞察产业格局,决胜品牌出海:2026年4月新发布亚马逊SPN超级工厂服务商深度选型指南 - 2026年企业推荐榜
  • 2026年现阶段,为何“河南瓦轴”成为华中轴承采购的优选伙伴? - 2026年企业推荐榜
  • 洞察2026年至今石家庄财务行业趋势:如何选择可靠的服务伙伴? - 2026年企业推荐榜
  • 2026年4月四川LED显示屏定制指南:如何精准选择可靠服务商 - 2026年企业推荐榜
  • 仓储库存为什么不能只用一个 stock 字段?一次讲清可用库存、锁定库存、在途库存与账本设计
  • 商业模式:从本质到落地的极简框架 - 智慧园区
  • NVIDIA Omniverse Kit 106:云端OpenUSD应用开发指南
  • 【电子通用硬件】MOS管振铃现象 什么是振铃?
  • 倍福TwinCAT 3 运动控制从入门到精通 | 03:硬件配置下篇——NC轴在线调试、参数标定与第三方伺服适配全流程详解
  • AI 协作工程化:用 perfect-cursor 打造高质量代码生成工作流
  • 2026年出国务工机构怎么选:劳务输出公司出国务工、正规出国务工劳务公司、正规出国务工机构、出国务工公司派遣、出国务工正规劳务公司选择指南 - 优质品牌商家