1、什么是Executor框架?
我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
2、Executor框架结构图解
2.1、Executor框架包括3大部分
(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。
(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。
Executor框架的成员及其关系可以用一下的关系图表示:
2.2、Executor框架的使用示意图
2.3、使用步骤
(1)创建Runnable并重写run()方法或者Callable对象并重写call()方法:
(2)创建Executor接口的实现类ThreadPoolExecutor类或者ScheduledThreadPoolExecutor类的对象,然后调用其execute()方法或者submit()方法把工作任务添加到线程中,如果有返回值则返回Future对象。其中Callable对象有返回值,因此使用submit()方法;而Runnable可以使用execute()方法,此外还可以使用submit()方法,只要使用callable(Runnable task)或者callable(Runnable task, Object result)方法把Runnable对象包装起来就可以,使用callable(Runnable task)方法返回的null,使用callable(Runnable task, Object result)方法返回result。
(3)调用Future对象的get()方法后的返回值,或者调用Future对象的cancel()方法取消当前线程的执行。最后关闭线程池
3、Executor框架成员
4、Executor 接口
4.1、Executor 接口简介
该接口提供了一种优雅的方式去解耦
任务处理机制中的任务提交
和任务如何运行
(也包含线程的使用,调度)
4.2、继承关系
4.2、源码
public interface Executor {
// 执行任务
void execute(Runnable command);
}
5、ExecutorService 接口
5.1、ExecutorService 接口简介
ExecuteService代表的是Executors创建的线程池
submit提交的是Callable方法,返回Future,说明submit是有返回值的
execute执行的是Runnable方法,没有返回值
所以submit和execute的区别是提交的方法和是否有返回值关于 Callable 、Runnable 和 Future、FutureTask ,具体见:线程池系列 之 FutureTask 及其底层实现
Executor 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。
可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。shutdown() 方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的 ExecutorService 以允许回收其资源。
通过创建并返回一个可用于取消执行和/或等待完成的 Future,方法 submit 扩展了基本方法 Executor.execute(java.lang.Runnable)。方法 invokeAny 和 invokeAll 是批量执行的最常用形式,它们执行任务 collection,然后等待至少一个,或全部任务完成(可使用 ExecutorCompletionService 类来编写这些方法的自定义变体)。
Executors 类提供了用于此包中所提供的执行程序服务的工厂方法
5.2、源码
public interface ExecutorService extends Executor {
......
}
submit
// 提交一个需要返回结果的任务去执行,返回一个有结果的消息体,只有成功执行后,才会返回结果
<T> Future<T> submit(Callable<T> task);
// 提交一个不需要(!!!)返回结果的任务去执行,返回一个有结果的消息体,只有成功执行后,才会返回结果
Future<?> submit(Runnable task);
// 只有当任务成功被执行后,才会返回给定的结果
<T> Future<T> submit(Runnable task, T result);
invokeAll
// 将集合里面的方法批量执行,不管有没有执行完,都返回 原始的FuturnTask列表(完成还是取消,FuturnTask的状态可以获取)
// 结果可以通过 FuturnTask.get() 获取
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException;
invokeAny
// 提交一批任务信息,当其中一个成功的执行,没有返回异常的时候,就返回结果
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
shutdown 系列
// 在之前提交的,需要被执行的任务中,有序的进行关闭操作,并且此时不会再接受新的任务
// 如果此时所有的任务已经关闭的话,那么就不会起到什么效果,因为已经没有任务可关闭了
// 重点:不会马上关闭!!!
void shutdown();
// 企图关闭所有正在执行的任务,并且中断正在等待要执行的任务,返回一个包含正在等待的任务的列表
// 重点:立刻关闭!!!
List<Runnable> shutdownNow();
// 只有当所有的任务都成功执行,否则会一直处于阻塞状态,只有当一下情况发生时,才会中断阻塞
// 例如收到一个关闭的请求,或者超时发生、或者当前的线程被中断后
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
判断方法
// 如果线程已经关闭了,就返回true
boolean isShutdown();
// 如果所有的线程任务已经关闭了,就返回true
boolean isTerminated();
6、AbstractExecutorService 抽象类
6.1、AbstractExecutorService 抽象类简介
AbstractExecutorService是一个实现ExecutorService接口的非常重要的抽象类,它提供了ExecutorService接口的默认实现。一般情况下,我们可直接继承AbstractExecutorService来实现自己的任务执行器,AbstractExecutorService已经实现ExecutorService接口中的大部分抽象方法,我们自己主要去实现execute方法,此抽象类大降低了编写自定义的任务执行器的难度。我们常用的线程池执行器ThreadPoolExecutor和ForkJoin框架的执行器ForkJoinPool都是直接继承抽象类AbstractExecutorService。
AbstractExecutorService使用newTaskFor返回的RunnableFuture来实现submit,invokeAny和invokeAll方法,该方法默认为此程序包中提供的FutureTask类(FutureTask在线程池系列 之 FutureTask 及其底层实现做过详细说明,这里不再赘述)。 submit系列方法将创建一个关联的RunnableFuture,该关联的RunnableFuture将被执行并返回, 子类可以重写newTaskFor方法以返回RunTask以外的RunnableFuture实现。
6.2、源码分析
public abstract class AbstractExecutorService implements ExecutorService {
......
}
(1)封装任务系列
// 封装 Callable类型的task
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// 封装 Runnable类型的task
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
关于 FutureTask 具体见:线程池系列 之 FutureTask 及其底层实现
(2)submit 系列
3个方法的执行逻辑都是:
(1)task==null,抛出控制针异常
(2)封装方法,这里是不同的地方(Callable、Runnable+null、Runnable+result)
这一步最重要,封装方法就是将 task 和一个 FutureTask 关联起来(主要是FutureTask的状态、当前线程、等待结果队列),这样就可以实现了异步获取结果
(3)execute(ftask)
// 提交一个需要返回结果的任务去执行
public <T> Future<T> submit(Callable<T> task) {
if (task == null)
throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task); //封装任务
execute(ftask); //执行任务
return ftask; //返回结果
}
// 提交一个 不需要 返回结果的任务去执行
public Future<?> submit(Runnable task) {
if (task == null)
throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null); //封装任务
execute(ftask);
return ftask;
}
// 只有当任务成功被执行后,才会返回给定的结果
public <T> Future<T> submit(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result); //封装任务
execute(ftask);
return ftask;
}
(3)invokeAll 系列
本方法中有很多涉及 FuturnTask 里面的方法,详细见:线程池系列 之 FutureTask 及其底层实现
将集合里面的方法批量执行,不管有没有执行完,都返回 原始的FuturnTask列表(完成还是取消,FuturnTask的状态可以获取),结果可以通过 FuturnTask.get() 获取
invokeAll(Collection<? extends Callable<T>> tasks)
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
// 如果任务集为空,则抛出一个NullPointerException
if (tasks == null)
throw new NullPointerException();
// 创建一个和任务数量等大的ArrayList(任务列表!!!)
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
// 标志任务是否完成(注意 invokeAll是所有任务完成时,才能叫完成)
boolean done = false;
try {
// 将tasks里面的每个Callable封装成FuturnTask,添加到futures列表里面,并交给Executor#execute()方法执行
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f);
}
// 判断futures里面的Future是否执行结束,如果还没有完成,通过get()阻塞直到任务完成
// 也是就说执行完这一段代码,futures里面的每一个任务都是执行完成的情况
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> f = futures.get(i); // 获取列表第i个FuturnTask
// 如果任务没有完成,就通过get阻塞等到它完成;如果已经完成了,就不用管(注意,这里又不是要它的执行结果!!!)
if (!f.isDone()) { // 只要任务开始(不是NEW),就是Done
try {
f.get(); //阻塞方法,直到FuturnTask的状态为完成才返回(正常、异常等也算完成)
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
// 如果出现异常,自然不会经过 done = true;
}
}
// 到这里,所有任务都完成了!!!
done = true; //标志所有任务都完成了
return futures;
} finally {
// 如果有任务没有完成,就将没有完成的任务取消(注意,可能取消失败)
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
和上面方法基本一样,就是加了超时判断!!!
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout); // 转换时间格式
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks)
futures.add(newTaskFor(t));
// 计算终止时间(!!!)
final long deadline = System.nanoTime() + nanos;
final int size = futures.size();
for (int i = 0; i < size; i++) {
execute((Runnable)futures.get(i));
nanos = deadline - System.nanoTime(); //计算剩余时间
if (nanos <= 0L) //如果超时,就返回(!!!)
return futures;
}
for (int i = 0; i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) { // 如果没有完成
if (nanos <= 0L) // 超时,则返回剩余任务
return futures;
try {
f.get(nanos, TimeUnit.NANOSECONDS);
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
} catch (TimeoutException toe) {
return futures;
}
nanos = deadline - System.nanoTime(); //更新剩余时间
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
(4)invokeAny 系列
这2个方法就是调用了 doInvokeAny 方法!!!
提交一批任务执行,只要有一个执行完成,就返回他的结果,所以他返回的是最快执行完成的那个任务的结果;当不能获取任何结果时,抛出遇到的最后一个异常
invokeAny(Collection<? extends Callable<T>> tasks)
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
(5)doInvokeAny
关于 ExecutorCompletionService的介绍见:更好的异步执行:CompletionService 和 ExecutorCompletionService 源码分析
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
// 下面代码块就是处理异常
// 如果任务集合为null,抛出空指针异常
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
if (ntasks == 0) //如果任务数量为0,抛出参数异常
throw new IllegalArgumentException();
// 下面代码块是初始化 任务列表和执行器
// 大小为ntasks大小的 FuturnTask任务列表
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
// 一个更好的异步执行器:内部通过 结果阻塞队列 + Executor执行任务 + QueueingFuture将结果放入结果队列
// 传入this,将this作为作为Executor
ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);
// 为了提高效率,特别是在并行性有限的执行器中,请在提交更多任务之前检查以前提交的任务是否已完成
// 这种交错加上异常机制解释了主循环的混乱
try {
// 用于记录异常,当 不能获得任何结果时 抛出记录的最后一个异常
ExecutionException ee = null;
// 根据情况,计算终止时间(如果参数规定不超时,那么就是0)
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 任务集合 遍历器
Iterator<? extends Callable<T>> it = tasks.iterator();
// 启动一个任务,再渐进式启动其他任务,然后将任务数量减1,活动数量计1(submit就是提交、执行)
futures.add(ecs.submit(it.next()));
--ntasks;
int active = 1; //当前正在执行的任务数量(已经提交给ecs的)
for (;;) {
Future<T> f = ecs.poll(); // 从ecs的结果队列中取出任务(立刻返回)
// 如果结果队列为空
if (f == null) {
// 当前没有任务已完成,且还有任务未提交,就继续提交下一个任务
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
// 当前没有任务已完成、所有任务已提交 且没有任务在执行时,退出循环
else if (active == 0)
break;
// 当前没有任务完成、所有任务已提交 且还有任务在执行时、设置了超时,就超时等待
else if (timed) {
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
if (f == null)
throw new TimeoutException();
nanos = deadline - System.nanoTime();
}
// 当前没有任务完成、所有任务已提交 且还有任务在执行时、未设置超时,就不限时长地等待
else
f = ecs.take();
} //if()
// 注意,经过上面的if,有可能ecs.take()获取出来了使得f!=null
// 结果队列非空(有任务已完成):还在执行的任务数自减,返回结果
if (f != null) {
--active;
try {
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
} //for(;;)
// 说明没有任务正常执行完成返回结果,那么就抛出最后一个异常
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
// 取消其他任务(卡面poll就将完成任务从队列中删除了)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
7、ScheduledExecutorService 接口
7.1、简介
Java 定时任务可以用Timer + TimerTask来做,或者使用ScheduledExecutorService,使用ScheduledExecutorService有两个好处:
1. 如果任务执行时间过长,TimerTask会出现延迟执行的情况。比如,第一任务在1000ms执行了4000ms,第二个任务定时在2000ms开始执行。这里由于第一个任务要执行4000,所以第二个任务实际在5000ms开始执行。这是由于Timer是单线程,且顺序执行提交的任务
2. 如果执行任务抛出异常,Timer是不会执行会后面的任务的
使用ScheduledExecutorService可以避免上面两种情况,因为ScheduledExecutorService是线程池,有多个线程执行。
ScheduledExecutorService 可以做一些延迟执行的任务,或者周期性执行的任务,它在 JDK 中有一个实现类 ScheduledThreadPoolExecutor,后文再进行分析。
7.2、源码分析
public interface ScheduledExecutorService extends ExecutorService {
// 创建一次性操作(Runnable),该操作会在指定的延迟之后执行(其实就是创建了一个定时任务)
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
// 与上述方法类型,参数类型不同(Callable)
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
/*
* 创建并执行一个周期性的任务
* 执行时间分别为 initialDelay, initialDelay + period, initialDelay + 2*period, 以此类推
* 若有异常则停止后续的执行;若任务执行时间超过其周期,则后续执行会延迟开始
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
/*
* 创建并执行一个周期性的任务
* 该操作在给定的初始延迟之后首先启用,然后在一次执行结束和下一次执行开始之间使用给定的延迟。
* 该方法与上述有些易混淆,不同的地方在于:
* 上述方法是以每次开始执行的时间来计算;
* 而本方法是以每次执行结束的时间和下次开始执行的时间计算
*/
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}