ExecutorCompletionService是如何工作的呢

129 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

上一章节我们学习到ExecutorCompletionService的使用,用ExecutorCompletionService执行任务,并获取结果;那么ExecutorCompletionService是如何执行任务, 又是如何将任务的结果存储到完成队列中的呢?

  • 1、ExecutorCompletionService在submit任务时, 会创建一个QueueingFuture, 然后将创建的QueueingFuture丢给executor, 让executor完成任务的执行工作;
  • 2、QueueingFuture继承FutureTask类, 而FutureTask实现了两个接口Runnable和Future
FutureTask构造方法:

    public FutureTask(Callable<V> callable) {
      if (callable == null)
        throw new NullPointerException();
      this.callable = callable; //保存传入来的 Callable 接口的实现类对象
      this.state = NEW;       // 这个就是用来保存 FutureTask 的状态 初识时 是新建状态
    }
    
FutureTask重要的类:

    private Callable<V> callable; // 用于保存传入 FutureTask 对象的 Callable 对象

    private Object outcome; // 用于保存 Callable 当中 call 函数的返回结果

    private volatile Thread runner; // 表示正在执行 call 函数的线程

    private volatile WaitNode waiters;// 被 get 函数挂起的线程 是一个单向链表 waiters 表示单向链表的头节点
    static final class WaitNode {
      volatile Thread thread; // 表示被挂起来的线程
      volatile WaitNode next; // 表示下一个节点
      WaitNode() { thread = Thread.currentThread(); }
    }
  • 3、Runnable一般表示要执行的任务的过程, 而Future则表述执行任务的结果 (或者说是任务的一个句柄, 可获取结果, 取消任务等)
  • 4、FutureTask就是一个有结果可期待的任务, FutureTask实现了run()方法, 我们指定此方法一般是在在工作线程(不是submit线程) 执行的,可以看看源码:
    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);
          }
      }
  • 5、FutureTask构造的时候需要一个Callable参数, Callable表示一个任务的执行过程, 在run方法中恰好调用了Callable.call(), 也就是任务工作在工作线程中执行

  • 6、那么任务执行完了返回结果, 这个结果是要在submit线程(就是提交任务的线程)中使用的, 那么如何让submit线程可以访问到呢?

    答案也是在FutureTask类中, 我们可以看到run方法中执行任务(Callable.call())获取结果后, 会掉用一个set()方法, set方法源码如下:

    protected void set(V v) {
          if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
              outcome = v;
              UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
              finishCompletion();
          }
    }
  • 7、set()方法将获取的结果存储到FuturnTask的一个outcome字段中, 这个过程是同步的, 所以其他线程稍后访问是可以读取到值的;

  • 8、ExecutorCompletionService中的完成队列中正好存储的是FuturnTask的子类, 当然可以调用FutureTask的get()方法, FutureTask的get方法就是获取outcome值 (get()方法中调用了report()方法, report中返回了outcome字段)

  • 9、FuturnTask中委托的任务执行完成后, 会掉一个done()方法, 这个方法是个空方法, 而其子类QueueingFuture重写了此方法, 如下:

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

正式在此方法中把执行完的任务放置到完成队列中的!!

    private final BlockingQueue<Future<V>> completionQueue;

然后我们就可以在submit线程中从完成队列中取出任务句柄, 获取任务结果了!!!

  • 10、那么回过来我们再看下ExecutorCompletionService对线程池保存做了什么呢?
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>>();
}

我们可以知道ExecutorCompletionService包装线程池的时候默认会创建一个LinkedBlockingQueue队列,我们也可以指定使用什么队列;

  • 11、我们在看下包装后提交任务的submit()方法
     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;
    }

我们可以看到其提交任务的代码是:executor.execute(new QueueingFuture(f)) 和使用线程提交的方式多一层QueueingFuture

线程池的提交方法实现:

image.png

  • 12、那这个QueueingFuture是什么东西呢

image.png

QueueingFuture继承自 FutureTask,并且重写了done()方法,其方法把任务放到我们包装线程池创建的堵塞队列里面;就是当任务执行完成后,就会被放到队列里面去了。

  • 13、最后调用其take()方法,就是阻塞等待,等到的一定是能够获取的结果的future,然后再调用get()方法获取执行结果; take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入。