CompletionService的优势到底在哪里?

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的响应时间会短一些。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值