1.java.util.concurrent.CompletionService这个接口的作用是什么?
Java的原生Doc如下(只截取了一部分):A service that decouples the production of new asynchronous tasks from the consumption of the results of completed tasks. Producers{@code submit} tasks for execution。大概意思就是说这个类把要执行的异步任务和任务执行结果解耦。
2.CompletionService如何实现解耦?
CompletionService通过Executor执行异步任务,并用容器存放异步任务执行的结果。调用者通过take方法或poll方法获取异步任务的执行结果(通过这两个方法名我们就可以大概猜到存放执行结果的容器是Queue)。这个类的核心思想符合生产者-消费者模式,即Executor作为生产者,执行任务并把结果存放到队列中;调用者作为消费者从队列中消费任务执行结果。
CompletionService接口源码如下:
public interface CompletionService<V> {
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);
Future<V> take() throws InterruptedException;
Future<V> poll();
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}
3.CompletionService的实现细节
JDK提供的实现类是java.util.concurrent.ExecutorCompletionService。这个类源码分析如下:
3.1构造函数
构造函数入参Executor用来执行异步任务,BlockingQueue用来存放执行结果,默认使用的是LinkedBlockingQueue。属性AbstractExecutorService的作用后面会提到。
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) {
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;
}
}
3.2QueueingFuture
QueueingFuture是ExecutorCompletionService类的内部类,同时继承了FutureTask,重写了done方法,在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;
}
3.3submit执行方法
这个方法多次重载,核心思想就是把callable包装成QueueingFuture,然后提交线程池执行。源码如下:
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;
}
private RunnableFuture<V> newTaskFor(Callable<V> task) {
if (aes == null)
return new FutureTask<V>(task);
else
return aes.newTaskFor(task);
}
在把Callable转换成RunnableFuture时,jdk担心你的Executor是AbstractExecutorService的子类,又重写了newTaskFor方法,所以判断了aes是否为空,这也是AbstractExecutorService属性存在的意义(猜测)。
顺便帮大家回忆下这几个关键类的关系:
4.那为什么要使用CompletionService呢?
既然存放执行结果的容器时阻塞队列,那么如果队列位空,执行take操作也是会阻塞的?和future.get()、循环判断future.isDone()有什么区别呢?
当时我也有这样的疑惑,后来想到一种使用场景,代码如下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Future<String> resutl1 = executor.submit(new Task());
Future<String> resutl2 = executor.submit(new Task());
Future<String> resutl3 = executor.submit(new Task());
resutl1.get();
resutl2.get();
resutl3.get();
}
如果result1任务的执行时间很长,但是result2、result3的任务执行时间很短,那么result2、result3就因为result1的get阻塞而不能及时返回。CompletionService的阻塞队列解决了这个问题,先执行完成的任务会把执行结果会加入阻塞队列,队列的take操作会阻塞,但是从队列中拿出的future的get操作不会阻塞,因为加入队列的都是执行完的任务。
5.总结:
如果执行结果的返回顺序和任务的开始时间没有关系,也就是消费者不关心返回结果的顺序时,消费者总会拿到最新任务的执行结果,这时使用CompletionService的响应时间会短一些。