CompletionService 的核心目标是按任务完成顺序处理结果,不等待未完成任务;它通过阻塞队列解耦提交与取结果,避免普通 Future 顺序 get 导致的阻塞等待。

用线程池配合 CompletionService,核心目标就一个:谁先跑完,就先处理谁的结果,不等其他任务——特别适合一批耗时差异大、结果到达时间不确定的异步任务。
为什么不用普通线程池直接 get()?
如果用 ExecutorService.submit() 提交一堆 Future,再按顺序调用 future.get(),就会卡在第一个还没完成的任务上,后面哪怕早就跑完了也得干等。而 CompletionService 把“提交”和“取结果”解耦了,内部用阻塞队列缓存已完成的 Future,调用 take() 或 poll() 时自动返回最先完成的那个。
基本用法:用 ExecutorService + CompletionService 包装
最常用的是 ExecutorCompletionService,它需要一个已有的线程池:
- 创建固定大小线程池:
ExecutorService executor = Executors.newFixedThreadPool(4); - 包装成 CompletionService:
CompletionService<string> cs = new ExecutorCompletionService(executor);</string> - 提交任务(Callable):
cs.submit(() -> doSomething()); - 按完成顺序获取结果:
Future<string> completed = cs.take(); // 阻塞直到有结果</string>或cs.poll(); // 非阻塞,可能返回 null
典型场景:批量请求+快速响应
比如同时调用 5 个不同接口,哪个先返回就先解析、入库或通知,不用等最慢的那个:
立即学习“Java免费学习笔记(深入)”;
- 每个请求封装为
Callable<Response>,提交给CompletionService - 用循环
cs.take()拿结果,每次拿到就立即处理(如记录日志、更新缓存、发消息) - 注意:
take()返回的是Future,要调用.get()才真正拿到返回值(此时不会阻塞,因为已完成) - 可加超时控制:
cs.poll(3, TimeUnit.SECONDS)避免无限等待
注意事项和小技巧
CompletionService 不会自动关闭线程池,记得手动 shutdown;任务异常时 Future.get() 会抛出 ExecutionException,需捕获并处理;如果只关心前 N 个最快结果,可以用计数器配合 poll() 避免阻塞;多个 take() 调用之间是线程安全的,无需额外同步。


















