携手创作,共同成长!这是我参与「掘金日新计划 · 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;
线程池的提交方法实现:
-
12、那这个QueueingFuture是什么东西呢
QueueingFuture继承自 FutureTask,并且重写了done()方法,其方法把任务放到我们包装线程池创建的堵塞队列里面;就是当任务执行完成后,就会被放到队列里面去了。
- 13、最后调用其take()方法,就是阻塞等待,等到的一定是能够获取的结果的future,然后再调用get()方法获取执行结果; take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入。