CompletionService VS ExecutorService

应用场景:

    众所周知,当我们需要并发的执行多个task,可以用ExecutorService来submit, 若需要取得所有task执行结果可以遍历所有的FutureTask中通过FutureTask.get()返回结果,然后合并结果。

  但是这样有个问题,FutureTask.get()方法是个阻塞的方法,并且其结果返回的顺序取决于submit到executor中的顺序,换句话说先完成任务的task可能不是先返回,这种方式的使用效率不太高。

而CompletionService 就是来解决这个问题的,稍后将剖析CompletionService 的工作原理。

 

假如现在有个需求计算10次 1到1000000 相加的总和(逐一递增),使用ExecutorService demo代码如下:

public class ExecutorServiceTest {

	private static AtomicInteger sumCount = new AtomicInteger();

    public static void main(String[] args) {
    	long startTime = System.currentTimeMillis();
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<FutureTask<Integer>> futureTaskList = new ArrayList<FutureTask<Integer>>();

        for(int i=1; i<=100; i++) {
            Task task = new Task();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
            futureTaskList.add(futureTask);
            executor.submit(futureTask);
        }

        executor.shutdown();

        for(FutureTask<Integer> futureTask: futureTaskList) {
        	 System.out.println("主线程在执行任务");

             try {
             	int curCou = futureTask.get();
                 System.out.println(Thread.currentThread().getName()+" --task运行结果"+curCou);
                 sumCount.addAndGet(curCou);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             } catch (ExecutionException e) {
                 e.printStackTrace();
             }
        }
        System.out.println("所有任务执行完毕======total="+sumCount.get());
        long endTime = System.currentTimeMillis();
        System.out.println("---耗时="+(endTime-startTime));
    }
}
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"  --子线程在进行计算");
        Thread.sleep(1000);
        int sum = 0;
        for(int i=0;i<1000000;i++)
            sum += i;
        return sum;
    }
}

  


 

倘若使用CompletionService,demo代码如下

public class CompletionServiceTest {

    static ExecutorService pool = Executors.newFixedThreadPool(10);
    static CompletionService<Long> cService = new ExecutorCompletionService<Long>(pool);
    static AtomicLong totalSum = new AtomicLong();

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        for(int i=1; i<=10; i++) {
            cService.submit(new MyTask());
        }
        for(int i=1; i<=10; i++) {
            try {
                Future<Long> cur = cService.take();
                totalSum.addAndGet(cur.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        System.out.println("主线程在执行任务");
        System.out.println("所有任务执行完毕======total="+totalSum.get());
        long endTime = System.currentTimeMillis();
        System.out.println("---耗时="+(endTime-startTime));
    }

}

class MyTask implements Callable<Long>{
    @Override
    public Long call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"  --子线程在进行计算");
        Thread.sleep(1000);
        long sum = 0;
        for(long i=0;i<1000000;i++)
            sum += i;
        return sum;
    }
}

 

当然这个例子两种运行花费的时间相差无几,此处只是用来说明CompletionService的用法。举个例子,如果是一个下载文件服务,我们可以利用CompletionService从一堆的镜像站点中去遍历下载,从而就可以获取到某个镜像站点最先下载完毕的文件(此时取消掉其他的下载任务)。

 

我们知道CompletionService可以让先完成的任务先返回,那底层是如何实现的呢?

CompletionService源码分析

CompletionService接口的实现类ExecutorCompletionService有3个成员变量

public class ExecutorCompletionService<V> implements CompletionService<V> {
    private final Executor executor;
    private final AbstractExecutorService aes;
    private final BlockingQueue<Future<V>> completionQueue;
...}
  • executor:执行task的线程池,创建CompletionService必须指定
  • aes:主要用于创建待执行task
  • completionQueue:存储已完成状态的task,默认是基于链表结构的阻塞队列LinkedBlockingQueue

通过构造方法来初始化

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

通过submit方法来提交Runnable或Callable的task

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

  

 

 

具体的执行流程如下:

  1. 参数校验(task为空,抛异常)
  2. 将Callable或Runable的task包装成FutureTask
  3. 将FutureTask交由线程池executor来执行

可能有人要问,submit task且完成后什么时候加入completionQueue的?

如下代码可以看到,FutureTask被进一步封装成QueueingFuture,在done()方法中,会把完成的任务加入到completionQueue。而这个done()方法是由FutureTask提供的一个“”钩子“”

    /**
     * FutureTask extension to enqueue upon completion
     */
    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()方法执行流程如下

        public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);//设置处理结果
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();//调用finishCompletion() 删除和通知所有等待的线程
        }
    }
    
    /**
     * Removes and signals all waiting threads, invokes done(), and
     * nulls out callable.
     */
    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();//调用done(),声明任务完成

        callable = null;        // to reduce footprint
    }

最后看一下,BlockingQueue 的take() 和poll()方法

take()会阻塞,等待任务完成,而poll()若任务没有完成,直接返回null. 具体详见如下代码

    public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            //任务完成数大于0取队头,否则返回空
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    
    
    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {//若任务未完成,会阻塞
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

 

综上所诉,在合理的场景使用CompletionService将提高程序的执行效率。

posted @ 2017-11-30 11:04  椰香飘逸  阅读(171)  评论(0编辑  收藏  举报