CompletionService 产生背景
我们知道如果线程需要返回,可以使用Callable接口来实现具有返回值的线程。如果使用线程池技术的话,就需要将Callable任务submit给线程池,返回Future引用,然后利用Future引用的get方法获取整个线程池中的所有任务的运行结果。
CompletionService运行原理
CompletionService实际上可以看做是Executor和BlockingQueue的结合体。CompletionService接收要执行的任务,将执行完成的任务放到BlockingQueue中,然后通过put和take获得任务执行的结果。
通过继承关系我们可以看到CompletionService是接口,具体的实现还是要看ExecutorCompletionService,ExecutorCompletionService就是在构造函数中创建一个LinkedBlockingQueue(基于链表的无界队列),主要用来保存线程池Executor执行的结果。当submit提交任务的时候,会将任务封装成QueueingFuture,QueueingFuture继承自FutureTask,将任务包装成异步任务,提交给线程池,通过重写done方法,将执行完的任务放入到LinkedBlockingQueue中,然后通过LinkedBlockingQueue获取执行任务的结果
大体流程可以看如下图片:
ExecutorCompletionService
其实研究CompletionService就是研究的ExecutorCompletionService,因为只有一个实现类,下面就主要分析一下ExecutorCompletionService这个里面都做了哪些事情
ExecutorCompletionService 构造方法
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
public ExecutorCompletionService(Executor executor,
BlockingQueue<Future<V>> completionQueue) {
if (executor == null || completionQueue == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
this.completionQueue = completionQueue;
}
一共有两种方式创建,但是都需要使用线程池,就是结果队列可以自定义,也可以使用默认的LinkedBlockingQueue(无界的阻塞队列)
ExecutorCompletionService submit
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;
}
public Future<V> submit(Runnable task, V result) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task, result);
executor.execute(new QueueingFuture(f));
return f;
}
提交任务的方式也很简单,先将任务包装成RunnableFuture,然后再将任务包装成QueueingFuture
可以看到QueueingFuture继承自FutureTask,只不过重写了done方法
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
done方法是在线程任务结束的时候才调用的,当线程任务结束的时候,就会将任务放入到completionQueue任务结束队列里面去
ExecutorCompletionService take&poll
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
public Future<V> poll() {
return completionQueue.poll();
}
public Future<V> poll(long timeout, TimeUnit unit)
throws InterruptedException {
return completionQueue.poll(timeout, unit);
}
获取任务结果也很简单,就是通过异步队列获取任务结束的Future,然后通过get获取线程结果
ExecutorCompletionService 总结
其实CompletionService的源码特别简单,无非就是将任务放入到线程池中,然后将结果放入到结果队列中,然后从结果队列中获取结果,最后获得所有任务的执行结果
ExecutorCompletionService 注意点
- 通过ExecutorCompletionService提交异步任务的时候,在获取结果的时候可能会导致乱序,因为有的任务执行快,有的任务执行慢,所以放入到结果队列的时机也不同,在获取结果的时候就不是顺序获取,如果希望顺序获取,就只能重新进行排序
- 尽量不要将ExecutorCompletionService设置为成员变量,因为如果两个任务同时提交的时候,在获取结果的时候,任务一可能会获取任务二的人结果,任务二可能获取任务一的结果,因为他们两个任务共用一个结果队列,且这个队列不能保证顺序导致的
所以在使用ExecutorCompletionService的时候一定要注意获取结束任务的顺序问题
ExecutorCompletionService 优点
ExecutorCompletionService在有获取任务结果顺序可能和提交顺序不同的原因存在为什么还要使用呢?
- 在执行大量相互独立和通过任务的时候可以使用ExecutorCompletionService
- ExecutorCompletionService可以设置任务执行的过期时间,主要通过poll(long timeout, TimeUnit unit)来实现,如果没有在规定的时间内完成则取消任务
- ExecutorCompletionService可以让异步任务执行结果有序化,先执行完的先进入到阻塞队列中,可以轻松实现后续处理的有序性,避免不必要的等待