《线程池系列五》-ExecutorCompletionService原理分析

ExecutorCompletionService可以很好的配合线程池使用,它的内部封装了线程池(线程池需要在构造对象时传入),将提交的任务代理给线程池执行(但任务已经不再是FutureTask类型,而是FutureTask的子类QueueingFuture,QueueingFuture重写了done()方法,该方法在FutureTask类中是空实现),因为提交的任务被转换为QueueingFuture对象,该对象在任务处理完成之后,会主动将该任务放到ExecutorCompletionService维护的阻塞队列中,因此执行完成的任务都会被放到阻塞队列中,使用结果时,只需调用take()或者poll()方法获取即可。

使用示例

使用ExecutorCompletionService需要一下几步:

  1. 创建线程池对象
  2. 创建ExecutorCompletionService对象,将线程池对象作为参数。
  3. 向ExecutorCompletionService对象提交任务,任务最终还是会代理给线程池对象执行。
  4. 执行take()方法获取执行完成的任务,通过get()方法获取任务执行结果。

示例代码如下:

package com.feng.validation;

import java.util.concurrent.*;

/**
 * Created by xinfeng.xu on 2017/10/16.
 */
public class ExecutorCompletionServiceDemo {

    private int threadCount = Runtime.getRuntime().availableProcessors();
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(threadCount, threadCount,
            0, TimeUnit.SECONDS, new LinkedBlockingQueue(10),
            new ThreadPoolExecutor.DiscardPolicy());
    private ExecutorCompletionService service = new ExecutorCompletionService(executor);

    static class Task implements Callable{

        @Override
        public String call() throws Exception {
            return "OK";
        }
    }

    public void executeTasks() throws InterruptedException, ExecutionException {

        //提交任务
        for(int i=0; i<100; i++){
            service.submit(new Task());
        }
        //获取结果
        for(int i=0; i<100; i++) {
            //获取完成的任务,如果没有完成的任务,将会阻塞
            Future task = service.take();
            //获取任务结果
            System.out.println(task.get());
        }
    }
    public static void main(String[] args) {

        try {
            ExecutorCompletionServiceDemo demo = new ExecutorCompletionServiceDemo();
            demo.executeTasks();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

CompletionService接口

ExecutorCompletionService实现了CompletionService接口,该接口定义提交任务,获取执行完成任务的方法

  • 提交任务操作
    submit() 可以提交callable对象和runnable对象
  • 获取执行完成任务操作
    take( ) 阻塞式获取完成的任务
    poll( ) 非阻塞式获取完成任务
    poll(timeout) 有限阻塞获取完成任务

QueueingFuture类

ExecutorCompletionService的内部类,该类实现了FutureTask类,重写了done()方法(该方法在FutureTask中是空实现),该方法什么时候被调用的,可以回忆一下FutureTask的finishCompletion(),该方法在任务执行完成时调用,唤醒等待线程之后调用done()方法,FutureTask的finishCompletion()方法源码如下:

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }

    done();

    callable = null;        // to reduce footprint
}

QueueingFuture 源码如下:

private class QueueingFuture extends FutureTask {
    QueueingFuture(RunnableFuture task) {
        super(task, null);
        this.task = task;
    }
    protected void done() { completionQueue.add(task); }
    private final Future task;
}

从源码中可以看出,任务执行完成后,就会将任务添加到completionQueue的阻塞队列中。

成员变量

  • Executor executor
    线程池对象,执行任务由该对象执行
  • AbstractExecutorService aes
    该对象的主要作用是将runnable或者callable对象转换为FutureRunnable对象。由于不知道Executor具体是哪一种实现,因此如果是AbstractExecutorService的子类,那么就将executor强制转化为AbstractExecutorService类型,只是表明能够直接调用newTaskFor()方法而已。如果不是AbstractExecutorService类型,那么就直接装换为FutureTask类型。
    这里为什么不全都转化为FutrueTask类型的, 因为实现AbstractExecutorService的子类不一定都是使用的FutureTask类,可能是自己定义的类,要保证一致。
  • BlockingQueue> completionQueue
    存放执行完成的任务,done()方法中添加

构造方法

为三个成员变量赋值,其中executor参数是需要传入的。源码如下:

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>();
}

除此之外,还提供了一个可传入阻塞队列的构造方法,源码不再贴出。

转换任务类型方法newTaskFor()

该方法类型为protected方法,外部方法不能调用。
如果接收的executor是AbstractExecutorService的子类,那么直接调用它的newTaskFor()方法;如果不是,构造为FutureTask类,源码如下:

private RunnableFuture newTaskFor(Callable task) {

    if (aes == null)
        return new FutureTask(task);
    else
        return aes.newTaskFor(task);
}

submit()方法

先将提交的任务callable对象或者runnable对象,转换为RunnableFuture对象,然后将RunnableFuture对象转换为QueueingFuture 对象,并将该对象作为execute()方法提交。
这里需要主要两点:

  1. 必须提交QueueingFuture对象,因为只有该对象才会在执行完成的任务放到阻塞队列中
  2. 必须由execute()方法提交任务,不能使用submit()方法,因为如果使用submit()提交任务,任务的会被封装成FutureTask对象,就不在是QueueingFuture类型的任务了。
    源码如下:只展示一个submit()的源码,其他重载方法类似:
public Future submit(Callable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture f = newTaskFor(task);
    executor.execute(new QueueingFuture(f));
    return f;
}

获取执行完成任务方法

任务执行完成之后,就会将任务放到阻塞队列中,获取执行完成的任务就转换为去阻塞队列中获取元素的操作。
其中take() poll() poll(timeout)都是直接调用阻塞队列的方法。源码如下:

public Future take() throws InterruptedException {
    return completionQueue.take();
}

public Future poll() {
    return completionQueue.poll();
}

public Future poll(long timeout, TimeUnit unit)
        throws InterruptedException {
    return completionQueue.poll(timeout, unit);
}

欢迎扫描下方二维码,关注公众号,我们可以进行技术交流,共同成长

《线程池系列五》-ExecutorCompletionService原理分析_第1张图片
qrcode_for_gh_5580beb3cba1_430.jpg

你可能感兴趣的:(《线程池系列五》-ExecutorCompletionService原理分析)