ExecutorCompletionService 是包装 Executor 的工具类,用于按任务完成先后顺序获取结果;它通过内部队列封装 Future,提供 take()/poll() 获取最先完成的任务,而普通 Executor 需遍历 Future 调用 get() 造成阻塞等待。

ExecutorCompletionService 是什么,和普通 Executor 有啥区别
它不是用来“执行任务”的新线程池,而是套在 Executor 外面的一层包装,专为解决“任务完成时间不确定、但想按完成先后取结果”这个痛点。普通 Future.get() 是阻塞等某个特定任务,而 ExecutorCompletionService 提供 take() 和 poll(),能拿到**最先完成的那个** Future,不管它是第几个提交的。
常见错误现象:用 invokeAll() 或遍历 List<future></future> 调用 get(),结果卡在第一个慢任务上,后面快的也得干等。
- 必须传一个已存在的
Executor(比如Executors.newFixedThreadPool(4)),它自己不管理线程 -
submit()方法返回的Future不是原始线程池的,而是经过内部队列重包装的,完成即入队 - 底层用的是无界
LinkedBlockingQueue,所以take()永远不会因队列空而失败(除非 shutdown 后还有未完成任务)
怎么提交任务并按完成顺序取结果
核心就两步:先 submit,再 take/poll。注意别把 submit() 和 take() 放反了顺序,否则可能死等。
典型使用场景:发多个 HTTP 请求、查多个数据库分片、批量调用外部 API —— 它们耗时差异大,但你只想尽快处理已返回的结果。
立即学习“Java免费学习笔记(深入)”;
- 用
completionService.take()阻塞等待下一个完成的任务(推荐用于“全部处理完”逻辑) - 用
completionService.poll()非阻塞检查,适合配合超时或轮询控制 - 每次
take()返回的是Future<v></v>,要立刻调用get()拿结果(此时不会阻塞,因为已确认完成) - 如果任务抛异常,
get()会原样抛出ExecutionException,需自己捕获处理
Executor executor = Executors.newFixedThreadPool(3);
ExecutorCompletionService<String> cs = new ExecutorCompletionService<>(executor);
cs.submit(() -> { Thread.sleep(2000); return "task2"; });
cs.submit(() -> { Thread.sleep(500); return "task1"; });
<p>// 先拿到 task1,再拿到 task2
for (int i = 0; i < 2; i++) {
try {
String result = cs.take().get(); // 这里 get 不会阻塞
System.out.println(result);
} catch (ExecutionException e) {
e.getCause().printStackTrace();
}
}为什么不能直接用 CompletionService 接口变量
因为 CompletionService 是接口,没有构造方法,你必须用它的唯一实现类 ExecutorCompletionService。写成 CompletionService<T> cs = new ExecutorCompletionService<>(e) 没问题,但别误以为存在其他实现。
容易踩的坑:有人想“复用”同一个 ExecutorCompletionService 实例跨多个业务逻辑块,却忘了它内部的完成队列是共享的——前一批任务的残留 Future 可能混进后一批的 take() 结果里。
- 每个独立的多任务批次,建议新建一个
ExecutorCompletionService实例 - 如果共用线程池,没问题;但不要共用
ExecutorCompletionService实例本身 - 它不自动清理已完成的
Future引用,长期运行时要注意 GC 友好性(不过一般影响不大)
和 CompletableFuture.allOf / anyOf 比有什么取舍
CompletableFuture 更灵活,支持链式编排、组合、异常恢复;但如果你只想要“谁先回来谁先处理”,ExecutorCompletionService 更轻量、语义更直接、无额外对象创建开销。
性能与兼容性:前者 JDK 8+,后者 JDK 5+ 就有;后者在高吞吐、低延迟敏感场景下,对象分配更少(没那么多 CompletableFuture 中间状态)。
- 需要按完成顺序逐个处理 → 优先
ExecutorCompletionService - 需要“全部完成才统一处理”或“任一完成就中断其余” →
CompletableFuture.anyOf更合适 - 要处理依赖关系(B 依赖 A 结果)、重试、降级 → 别硬扛,上
CompletableFuture
真正容易被忽略的是:它不帮你关线程池。ExecutorCompletionService 自己没有 shutdown() 方法,你得记得调用底层 Executor 的 shutdown(),否则 JVM 可能无法退出。










