让我们深入解释一下 CompletionService 的底层实现原理。这个组件的实现其实非常巧妙,它主要通过 ExecutorCompletionService 类来实现。
首先,让我们看看 ExecutorCompletionService 的核心组件:
public class ExecutorCompletionService<V> implements CompletionService<V> {
private final Executor executor; // 执行任务的执行器
private final AbstractExecutorService aes; // 用于包装任务
private final BlockingQueue<Future<V>> completionQueue; // 完成队列
// 构造函数
public ExecutorCompletionService(Executor executor) {
this(executor, new LinkedBlockingQueue<Future<V>>());
}
}
核心实现原理可以分为以下几个关键部分:
- 完成队列的实现
private final BlockingQueue<Future<V>> completionQueue;
这是整个实现的核心,它使用了阻塞队列来存储已完成的任务。默认使用 LinkedBlockingQueue,它是一个无界队列,特点是:
- 线程安全
- 支持阻塞操作
- 按照 FIFO(先进先出)顺序排列完成的任务
- 任务包装机制
private class QueueingFuture extends FutureTask<Void> {
private final Future<V> task;
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() {
completionQueue.add(task);
}
}
这个内部类非常关键,它:
- 继承自 FutureTask
- 重写了 done() 方法
- 当任务完成时,自动将结果放入完成队列
- 任务提交过程
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}
当提交任务时:
-
将 Callable 任务包装成 RunnableFuture
-
再包装成 QueueingFuture
-
提交给执行器执行
-
结果获取机制
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
public Future<V> poll() {
return completionQueue.poll();
}
结果获取提供了两种模式:
- take():阻塞等待
- poll():非阻塞立即返回
让我们通过一个流程图来理解整个工作过程:
为了更好地理解这个机制,让我们看一个具体的使用场景示例:
public class ParallelDataProcessor {
private final CompletionService<ProcessResult> completionService;
private final int timeout;
public List<ProcessResult> processDataParallel(List<Data> dataList) {
List<ProcessResult> results = new ArrayList<>();
// 提交所有任务
int taskCount = 0;
for (Data data : dataList) {
completionService.submit(() -> processData(data));
taskCount++;
}
// 收集结果(带超时处理)
for (int i = 0; i < taskCount; i++) {
try {
Future<ProcessResult> future =
completionService.poll(timeout, TimeUnit.SECONDS);
if (future != null) {
results.add(future.get());
} else {
handleTimeout();
}
} catch (Exception e) {
handleError(e);
}
}
return results;
}
}
CompletionService 的设计体现了以下几个重要的设计原则:
-
关注点分离:
- 任务执行与结果收集完全分离
- 执行器负责执行任务
- 完成队列负责管理结果
-
组合优于继承:
- 使用组合方式集成了 Executor 和 BlockingQueue
- 避免了继承带来的强耦合
-
装饰器模式:
- 通过 QueueingFuture 装饰原始任务
- 在不改变原有任务的情况下增加了新功能
这种实现方式带来的优势是:
-
高效性:
- 无需轮询任务状态
- 完成的任务自动进入队列
-
灵活性:
- 可以使用不同类型的执行器
- 可以替换不同的完成队列实现
-
可靠性:
- 线程安全的实现
- 完善的异常处理机制
如果有疑问,欢迎评论区留言讨论