CompletionService

1 CompletionService介绍

CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。
如果你向Executor提交了一个批处理任务,并且希望在它们完成后获得结果。为此你可以将每个任务的Future保存进一个集合,然后循环这个集合调用Future的get()取出数据。幸运的是CompletionService帮你做了这件事情。
CompletionService整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take和poll方法,在结果完整可用时获得这个结果,像一个打包的Future。
CompletionService的take返回的future是哪个先完成就先返回哪一个,而不是根据提交顺序。

2 CompletionService源码分析

首先看一下 构造方法:

  1. public ExecutorCompletionService(Executor executor) {
  2. if (executor == null)
  3. throw new NullPointerException();
  4. this.executor = executor;
  5. this.aes = (executor instanceof AbstractExecutorService) ?
  6. (AbstractExecutorService) executor : null;
  7. this.completionQueue = new LinkedBlockingQueue<Future<V>>();
  8. }

构造法方法主要初始化了一个阻塞队列,用来存储已完成的task任务。
然后看一下 completionService.submit 方法:

  1. public Future<V> submit(Callable<V> task) {
  2. if (task == null) throw new NullPointerException();
  3. RunnableFuture<V> f = newTaskFor(task);
  4. executor.execute(new QueueingFuture(f));
  5. return f;
  6. }
  7. public Future<V> submit(Runnable task, V result) {
  8. if (task == null) throw new NullPointerException();
  9. RunnableFuture<V> f = newTaskFor(task, result);
  10. executor.execute(new QueueingFuture(f));
  11. return f;
  12. }

可以看到,callable任务被包装成QueueingFuture,而 QueueingFuture是 FutureTask的子类,所以最终执行了FutureTask中的run()方法。来看一下该方法:

  1. public void run() {
  2. //判断执行状态,保证callable任务只被运行一次
  3. if (state != NEW ||
  4. !UNSAFE.compareAndSwapObject(this, runnerOffset,
  5. null, Thread.currentThread()))
  6. return;
  7. try {
  8. Callable<V> c = callable;
  9. if (c != null && state == NEW) {
  10. V result;
  11. boolean ran;
  12. try {
  13. //这里回调我们创建的callable对象中的call方法
  14. result = c.call();
  15. ran = true;
  16. } catch (Throwable ex) {
  17. result = null;
  18. ran = false;
  19. setException(ex);
  20. }
  21. if (ran)
  22. //处理执行结果
  23. set(result);
  24. }
  25. } finally {
  26. runner = null;
  27. // state must be re-read after nulling runner to prevent
  28. // leaked interrupts
  29. int s = state;
  30. if (s >= INTERRUPTING)
  31. handlePossibleCancellationInterrupt(s);
  32. }
  33. }

可以看到在该 FutureTask 中执行run方法,最终回调自定义的callable中的call方法,执行结束之后,通过 set(result) 处理执行结果:

  1. /**
  2. * Sets the result of this future to the given value unless
  3. * this future has already been set or has been cancelled.
  4. *
  5. * <p>This method is invoked internally by the {@link #run} method
  6. * upon successful completion of the computation.
  7. *
  8. * @param v the value
  9. */
  10. protected void set(V v) {
  11. if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
  12. outcome = v;
  13. UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
  14. finishCompletion();
  15. }
  16. }

继续跟进finishCompletion()方法,在该方法中找到 done()方法:

  1. protected void done() { completionQueue.add(task); }

可以看到该方法只做了一件事情,就是将执行结束的task添加到了队列中,只要队列中有元素,我们调用take()方法时就可以获得执行的结果。
到这里就已经清晰了,异步非阻塞获取执行结果的实现原理其实就是通过队列来实现的,FutureTask将执行结果放到队列中,先进先出,线程执行结束的顺序就是获取结果的顺序。
CompletionService实际上可以看做是Executor和BlockingQueue的结合体。CompletionService在接收到要执行的任务时,通过类似BlockingQueue的put和take获得任务执行的结果。CompletionService的一个实现是ExecutorCompletionService,ExecutorCompletionService把具体的计算任务交给Executor完成。
在实现上,ExecutorCompletionService在构造函数中会创建一个BlockingQueue(使用的基于链表的无界队列LinkedBlockingQueue),该BlockingQueue的作用是保存Executor执行的结果。当计算完成时,调用FutureTask的done方法。当提交一个任务到ExecutorCompletionService时,首先将任务包装成QueueingFuture,它是FutureTask的一个子类,然后改写FutureTask的done方法,之后把Executor执行的计算结果放入BlockingQueue中。QueueingFuture的源码如下:

  1. /**
  2. * FutureTask extension to enqueue upon completion
  3. */
  4. private class QueueingFuture extends FutureTask<Void> {
  5. QueueingFuture(RunnableFuture<V> task) {
  6. super(task, null);
  7. this.task = task;
  8. }
  9. protected void done() { completionQueue.add(task); }
  10. private final Future<V> task;
  11. }

3 CompletionService实现任务
  1. public class CompletionServiceTest {
  2. public static void main(String[] args) {
  3. ExecutorService threadPool = Executors.newFixedThreadPool(10);
  4. CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool);
  5. for (int i = 1; i <=10; i++) {
  6. final int seq = i;
  7. completionService.submit(new Callable<Integer>() {
  8. @Override
  9. public Integer call() throws Exception {
  10. Thread.sleep(new Random().nextInt(5000));
  11. return seq;
  12. }
  13. });
  14. }
  15. threadPool.shutdown();
  16. for (int i = 0; i < 10; i++) {
  17. try {
  18. System.out.println(
  19. completionService.take().get());
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. } catch (ExecutionException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }
  1. 7
  2. 3
  3. 9
  4. 8
  5. 1
  6. 2
  7. 4
  8. 6
  9. 5
  10. 10

CompletionService总结

相比ExecutorService,CompletionService可以更精确和简便地完成异步任务的执行 CompletionService的一个实现是ExecutorCompletionService,它是Executor和BlockingQueue功能的融合体,Executor完成计算任务,BlockingQueue负责保存异步任务的执行结果 在执行大量相互独立和同构的任务时,可以使用CompletionService CompletionService可以为任务的执行设置时限,主要是通过BlockingQueue的poll(long time,TimeUnit unit)为任务执行结果的取得限制时间,如果没有完成就取消任务

转:https://www.jianshu.com/p/f8b0f07abf22