ExecutorCompletionService 的核心价值在于“谁先完成,谁先被处理”,它通过内部 LinkedBlockingQueue 实现完成任务的自动入队,使 take()/poll() 能优先获取最先完成的 Future,避免按提交顺序阻塞等待,从而支持快任务优先处理。

ExecutorCompletionService 的核心价值在于“谁先完成,谁先被处理”,它不改变任务执行逻辑,而是重构了结果获取方式——把被动等待变成主动响应。
为什么普通 Future 列表会卡住
直接用 ExecutorService 提交一批任务,得到 List
- invokeAll() 同样要等最慢的那个任务结束才返回整个列表
- 遍历中调用 get() 时,遇到第一个未完成的 Future 就停住,后续已完成的也被晾着
- 无法体现“快任务优先处理”的业务意图,比如接口聚合、爬虫解析、多源搜索等场景
ExecutorCompletionService 怎么打破这个瓶颈
它在 Executor 外加了一层包装,内部维护一个 LinkedBlockingQueue
- submit() 返回的 Future 已被重包装,完成即入队,无需手动干预
- take() 阻塞直到有结果可用,适合“必须拿到一个就立刻处理”的主循环
- poll() 立即返回,为空则继续做别的事,适合配合轮询或超时控制
- 从队列取出 Future 后调用 get() 不会阻塞,因为该 Future 已确认完成
如何提交并安全取结果
典型流程是先批量 submit,再循环 take,直到取够预期数量。注意别在 submit 前就调 take,否则可能空等;也不建议一边 submit 一边 take,除非明确需要流式处理。
- 每次 take() 返回 Future
,应立即调用 get() 获取真实结果,避免 Future 泄漏 - 任务若抛异常,get() 会原样抛出 ExecutionException,需捕获 getCause() 取原始异常
- 为区分来源,可在 Callable 中返回自定义对象(如 Result{taskId, data, cost}),而不是裸字符串
- 线程池需自行管理生命周期,CompletionService 关闭后不会自动 shutdown executor
不能直接用 CompletionService 接口的原因
CompletionService 是接口,没有构造方法,也没有默认实现。Java 并发包只提供了一个具体实现类:ExecutorCompletionService。它必须传入一个已存在的 Executor 实例(如 newFixedThreadPool),自己不创建也不管理线程。
- 构造时可选传入 BlockingQueue,不传则默认用无界 LinkedBlockingQueue
- 这个队列决定了结果排队行为,无界队列保证 take() 不会因容量满而失败
- 所有 submit/take/poll 行为都围绕这个队列展开,本质是“执行”与“结果消费”的解耦










