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; }
具体的执行流程如下:
- 参数校验(task为空,抛异常)
- 将Callable或Runable的task包装成FutureTask
- 将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将提高程序的执行效率。