前言
相关系列
- 《Java & Executor & 目录》
- 《Java & Executor & QueueingFuture & 源码》
- 《Java & Executor & QueueingFuture & 总结》
- 《Java & Executor & QueueingFuture & 问题》
涉及内容
- 《Java & Executor & Future & 总结》
- 《Java & Executor & RunnableFuture & 总结》
- 《Java & Executor & FutureTask & 总结》
- 《Java & Executor & ExecutorCompletionService & 总结》
概述
简介
QueueingFuture @ 排队未来类是由ExecutorCompletionService @ 执行器完成服务类内部实现的私有FutureTask @ 未来任务类子类,作用是借助未来任务类的自定义/钩子方法实现令任务在执行结束(完成/异常/取消)时自动加入[completionQueue @ 完成队列]的功能。由于排队未来类并没有对未来任务类进行任何侵入性的重写,即排队任务类没有对未来任务类的现有功能/功能进行任何改动,因此本文不会对排队未来类继承自未来任务类的相关内容进行阐述,而是将重点集中在其额外衍生拓展的部分。如果对未来任务类的实现机制/原理有一定需求可通过上文中的链接跳转至相关文章。
实现
相比于未来任务类而言,排队任务类共新增了两方面的改动:一是新组合了Future @ 任务接口类型的字段[task @ 任务]用于持有代表任务的引用;二是重写了未来任务类定义的done()方法使得代表任务在执行结束时会被自动加入至关联执行器完成服务的[完成队列]中…关键源码如下:
private final Future<V> task;
@Override
protected void done() {
completionQueue.add(task);
}
[任务]被排队未来类用于持有代表任务的引用。这是一个可能令人产生疑惑的点,因为已知的是排队未来类的父类未来任务类本身就组合了用于持有代表任务引用的Callable @ 可调用类型字段[callable @ 可调用],那为什么排队未来类还要再设计一个相同功能的字段呢?这是因为[可调用]是一个无法被继承的私有字段,并且未来任务类也没有将之作为属性,即没有为之实现相应的set()/get()方法,因此排队未来类实际上并无法在其内部代码中获取到代表任务的引用。但由于其又需要在done()方法中将代表任务加入至关联执行器完成服务的[完成队列],因此才会新组合[任务]用于持有代表任务的引用。
排队未来的代表任务是被封装为RunnableFuture @ 可运行未来的递交任务。我们可以从源码中发现[任务]被设计为与[可调用]不同的未来接口类型,该设计与执行器完成服务类的使用场景有关。执行器完成服务类被AbstractExecutorService @ 抽象执行器服务抽象类用于实现执行器服务接口定义的doInvokeAny(…)方法,该方法在设计上用于递交一组任务并等待至其中的一个完成后返回执行结果。常规的任务递交/执行是无法获取到结果的,即直接调用Executor @ 执行器接口定义的execute(Runnable command)方法只能单纯递交/执行任务而不会有任何回应。因此在执行器框架中想要获取递交任务的执行结果就必须在递交/执行任务的同时获取到与之关联的未来,从而通过get()方法获取保存在未来中的执行结果。这其中的典型代表便是执行器服务接口定义的submit @ 递交方法,因为该方法在递交任务的同时会返回任务的关联未来以供追踪/获取任务的执行状态/结果。事实上抽象执行器服务类对于递交方法的默认实现就是将递交任务封装为可运行未来后再内部调用execute(Runnable command)方法执行。
为了令调用任意方法也可以获取到递交任务的执行结果,抽象执行器服务抽象类同样会将递交任务封装为可运行未来后再执行。而又因为执行器完成服务类在设计上将[完成队列]作为已结束递交任务的存储容器,因此递交任务实际上是以可运行未来的形式加入[完成队列]的。由此可知用于将递交任务在结束时添加至[完成队列]的排队未来实际上是由可运行未来封装而来而非直接由递交任务封装而来,即排队未来的代表任务是可运行未来而非递交任务。而又由于抽象执行器服务抽象类默认将可运行未来接口实现类的未来任务类作为递交任务的封装类,即递交任务默认会被封装为未来任务,因此排队未来的代表任务通常即为未来任务。
为什么不将递交任务直接封装为排队未来呢?这确实是一个值得讨论的问题。首先我们可以确定是的排队未来类作为未来任务类的子类其必然也是可以追踪/获取任务的执行状态/结果的, 此外其对代表任务的封装逻辑也与未来任务类完全相同,因此将递交任务直接封装为排队未来似乎确实是可行的。但我们必须明白抽象执行器服务抽象类虽然默认将未来任务类作为递交任务的封装类,但这并非表示其只能使用未来任务类对递交任务进行封装。开发者完全可以重写newTaskFor(…)方法令抽象执行器服务抽象类使用其它可运行接口实现类封装递交任务,而该情况下如果直接将递交任务封装为排队未来就会破坏抽象执行器服务抽象类对递交任务的封装逻辑,因此递交任务只能被二次封装为排队未来。