Executor框架使用
Java内部提供了线程池Executor框架,其功能比之前笔记中自实现的简易线程池更加全面。
1.整体架构
Executor框架的整体架构如下,
接口/类 | 说明 |
---|---|
Executor | 该接口只有一个execute 方法,目的是将任务提交和执行解耦 |
ExecutorService | 该接口对Executor接口进行了拓展,定义了对线程池中线程的管理方法 |
AbstractExecutorService | 对ExecutorService接口中部分方法的实现 |
ThreadPoolExecutor | 线程池的最终实现,实现了线程池工作的完整机制,是整个框架的重点 |
ForkJoinPool | 实现了Fork/Join模式的线程池 |
ScheduleExecutorService | 对ExecutorService进行了拓展,定义了延迟执行和周期执行任务的方法 |
ScheduleThreadExecutorService | 在继承ThreadPoolExecutorService的基础上对ScheduleExecutorService接口进行了实现 |
2.源码解析
Executor接口
该接口只有一个方法executor
,传入参数是 Runnable类型,
public interface Executor {
void execute(Runnable command);
}
ExecuorService接口
该接口对 Executor接口进行了拓展,定义了线程池管理和更多执行任务的方法,
重要的方法说明,
方法 | 说明 |
---|---|
shutdown | 终止ExecutorService,不再接收和执行新的任务,已经执行的任务会被执行完 |
shutdownNow | 立刻终止ExecutorService,不再接收和执行新的任务,已经执行的任务也会终止(但不保证能够被终止) |
submit | 对execute 方法的拓展,返回一个 Future类对象 |
invokeAll | 执行一组任务,所有任务都返回或者 timeout 的时候,invokeAll 方法返回执行结果列表。该方法一旦返回结果,没有完成的任务则被取消 |
invokeAny | 执行一组任务,任意一个任务有返回时,invokeAny 返回该任务的执行结果。其余没有完成的任务则被取消。 |
AbstractExecutorService类
该抽象类封装了 Executor的很多通用功能,最重要的两个方法是newTaskFor
和submit
方法。
1)构建任务—newTaskFor方法
该方法的实现有两种形式,分别针对 Runnable和 Callable接口的实现类,
// 传入参数为 Runnable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
// 传入参数为 Callable
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
返回值的类型是 FutureTask类对象,属于 RunnableFuture接口的实现类。该类的具体实现参见笔记,可简单理解为将Runnable和Callable统一封装为Callable对象。
2)构建任务并执行—submit方法
该方法的实现同样依据传入的参数是 Runnable和Callable分为两种,
// 提交无返回值的任务
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// ftask 其实是 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// 提交有返回值的任务
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// ftask 其实是 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
对该抽象类进行如下说明,
- AbstractExecutorService类最主要的是实现了
submit
方法,用于提交和执行任务。其内部调用了自身实现的newTaskFor
方法和Executor接口定义的execute
方法。
在newTaskFor
方法中构建的任务是FutureTask类对象,属于RunnableFuture类的子类。RunnableFuture实现类Runnable接口和Future接口,所以可以被传入 Executor接口的execute
方法 execute
方法的具体实现是在该类的子类 ThreadPoolExecutor中
ThreadPoolExecutor类*
1)类注释
ThreadPoolExecutor类的注释很多,关键注释如下,
- 线程池解决两个问题,通过减少任务间的调度开销提高大量任务时的执行性能(主要是线程被重复使用);提供一种方式管理线程的消费,维护基本数据并对已完成任务进行统计
- Executors 为常用的场景设定了可直接初始化线程池的方法,比如
Executors#newCachedThreadPool
无界的线程池,并且可以自动回收;
Executors#newFixedThreadPool
固定大小线程池;
Executors#newSingleThreadExecutor
单个线程的线程池等 - 为了在各种上下文中使用线程池,线程池提供可供扩展的参数设置,
coreSize
:当新任务提交时,发现运行的线程数小于 coreSize,一个新的线程将被创建,即使这时候其它工作线程是空闲的,可以通过 getCorePoolSize 方法获得 coreSize;
maxSize
:当任务提交时,coreSize < 运行线程数 <= maxSize,但队列没有满时,任务提交到队列中,如果队列满了,在 maxSize 允许的范围内新建线程 - 默认的,core threads 需要到任务提交后才创建的,但可以分别使用
prestartCoreThread
和prestartAllCoreThreads
两个方法来提前创建一个、所有的 core threads - 新的线程被默认 ThreadFactory 创建时,优先级会被限制成
NORM_PRIORITY
,默认会被设置成非守护线程,这个和新建线程的继承是不同的 Keep-alive times
参数的作用,
i. 如果当前线程池中有超过 coreSize 的线程;
ii. 并且线程空闲的时间超过 keepAliveTime,当前线程就会被回收,这样可以避免线程没有被使用时的资源浪费- 如果设置
allowCoreThreadTimeOut
为 ture 的话,core thread 空闲时间超过 keepAliveTime 的话,也会被回收 - 线程池新建时,有多种任务队列可供选择,
i. SynchronousQueue,为了避免任务被拒绝,要求线程池的 maxSize 无界,缺点是当任务提交的速度超过消费的速度时,可能出现无限制的线程增长;
ii. LinkedBlockingQueue,无界队列,未消费的任务可以在队列中等待;
iii. ArrayBlockingQueue,有界队列,可以防止资源被耗尽 - 在 Executor 已经关闭或对最大线程和最大队列都使用饱和时,可以使用
RejectedExecutionHandler
类进行异常捕捉,有如下四种处理策略:ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.DiscardPolicy、ThreadPoolExecutor.CallerRunsPolicy、ThreadPoolExecutor.DiscardOldestPolicy
10.线程池提供了很多可供扩展的钩子函数,
i. 提供在每个任务执行之前beforeExecute
和执行之后afterExecute
的钩子方法,主要用于操作执行环境,比如初始化 ThreadLocals、收集统计数据、添加日志条目等;
ii. 如果在执行器执行完成之后想干一些事情,可以实现terminated
方法
如果钩子方法执行时发生异常,工作线程可能会失败并立即终止
2)ThreadPoolExecutor重要属性
用户可控制的属性都是 volatile
关键字修饰的,
属性 | 说明 |
---|---|
private final AtomicInteger ctl | 线程池状态控制字段由两部分组成,工作线程数wc 和线程池状态rs ,具体见表格下方说明 |
volatile long completedTasks | 已完成任务的计数 |
private long completedTaskCount | 已经完成的任务数 |
private int largestPoolSize | 线程池最大容量 |
private volatile ThreadFactory threadFactory | 可以使用 threadFactory 创建 线程 |
private volatile RejectedExecutionHandler handler | 饱和或者运行中拒绝任务的 handler 处理类 |
private volatile long keepAliveTime | 线程存活时间设置 |
private volatile boolean allowCoreThreadTimeOut | 设置 true 的话,核心线程空闲 keepAliveTime 时间后,也会被回收 |
private volatile int corePoolSize | 核心线程数目 |
private volatile int maximumPoolSize | 用户设定的线程池最大线程数 |
private static final RejectedExecutionHandler defaultHandler | 默认的拒绝策略,默认是 AbortPolicy类对象 |
private final BlockingQueue workQueue | 任务队列,利用队列的阻塞的特性 |
private final HashSet workers | 包含线程池中所有的工作线程,元素是Worker类对象 |
private final ReentrantLock mainLock | 可重入锁对象,控制对workers队列的访问 |
private final Condition termination | 可重入锁对象mainLock 的条件队列,控制对workers队列的访问 |
工作线程数—workerCount
相关属性和方法,
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // =(2^29)-1=536870911
private static int workerCountOf(int c) {
return c & CAPACITY;
}
线程池状态—runState
线程池相关状态,
private static final int RUNNING = -1 << COUNT_BITS; //-536870912
private static final int SHUTDOWN = 0 << COUNT_BITS; //0
private static final int STOP = 1 << COUNT_BITS; //-536870912
private static final int TIDYING = 2 << COUNT_BITS; //1073741824
private static final int TERMINATED = 3 << COUNT_BITS; //1610612736
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
线程池状态转换,
状态 | 说明 |
---|---|
RUNNING(-536870912) | 接受新任务或者处理队列里的任务。 |
SHUTDOWN(0) | 不接受新任务,但仍在处理已经在队列里面的任务。执行 shutdown() 和finalize() 方法后从 RUNNING到 SHUTDOWN状态 |
STOP(-536870912) | 不接受新任务,也不处理队列中的任务,对正在执行的任务进行中断。执行 shutdownNow() 方法后从 RUNNING到 STOP状态 |
TIDYING(1073741824) | 所以任务都被中断,workerCount 是 0,整理状态。SHUTDOWN/STOP -> TIDYING -> workerCount=0 |
TERMINATED(1610612736) | 执行terminated() 方法执行完成时从 TIDYING到 TERMINATED |
线程池状态控制字段—ctl
ctl
变量由工作线程数和线程池状态共同构成,该变量是一个 AtomicInteger 的类,就是让保存的 int 变量的更新都是原子操作,保证线程安全。
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
ctlOf
方法就是组合运行状态和工作线程数量,该方法是通过按位或的方式来实现的。这里把一个 int 变量拆成两部分来用。前面3位用来表示状态,后面29位用来表示工程线程数量。所以,工作线程数量最大不能超过
2
29
−
1
2^{29}-1
229−1。
该变量一会儿用来获取线程数量workerCountOf
方法,一会儿又用来判断线程池是否处于运行状态isRunning
方法。这两个方法源码如下,
// 这两个方法的参数c就是当前线程池 ctl的值
private static int workerCountOf(int c) {
return c & CAPACITY;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
第一个方法中又多出一个CAPACITY
常量,
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
CAPACITY
的值为
000111...111
000111...111
000111...111(共计32位)。
workerCountOf
方法中按位与之后去掉了前面3位,保留了后面29位。所以,拿到的就是工作线程的数量。
isRunning
方法中,直接拿 ctl
的值和 SHUTDOWN
作比较。首先明确在 RUNNING
状态下,ctl
的值是什么样的,
- 初始状态,
ctl
的值是 11100000... … 00000000 11100000 ... … 00000000 11100000...…00000000,表示RUNNING
状态,和0个工作线程。 - 每创建一个新线程,都把
ctl
加1。当有5个工作线程时,ctl
的值是 11100000... … 00000101 11100000 ... … 00000101 11100000...…00000101。
在 RUNNING
状态下,ctl
始终是负值,而 SHUTDOWN
是0,所以可以通过直接比较 ctl
的值来确定状态。
3)构造方法
一般并不直接通过ThreadPoolExecutor的构造方法创建线程池,而是通过Executors类提供的工厂函数进行创建,
Executors.newFixedThreadPool(10)
通过 Executors 的工厂方法来创建ThreadPoolExecutor对象。Executor 提供了多种工厂方法创建线程池。其实根本是调用 ThreadPoolExecutor 构造方法时传入参数不同。以newFixedThreadPool
方法为例,源代码如下,
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
底层调用的 ThreadPoolExecutor类的构造方法如下,
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数说明,
参数 | 说明 |
---|---|
corePoolSize | 线程池核心线程数量,即最小线程数量。不设置allowCoreThreadTimeout 时,核心线程一直存活 |
maximumPoolSize | 线程池最大线程数量,受限于CAPACITY 的大小 |
workQueue | 一个阻塞队列,用于保存线程池要执行的所有任务 |
threadFactory | 规范了生成的线程,避免了手动调用new Thread() 时创建出来的线程的差异,创建得到的线程具有连续的线程号码 |
构造过程中没有启动任何线程,避免对资源的浪费。
4)内部封装线程—Worker类
Worker封装了线程,是线程池中的工作单元。该类继承了 AQS,同时实现了 Runnable。
类属性
该类的属性如下,
// 该worker对象中封装的线程,如果是 null说明ThreadFactory构建线程失败
final Thread thread;
// 需要执行的任务
Runnable firstTask;
// 记录每个线程完成的任务数目
volatile long completedTasks
构造方法
构造方法如下,
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
创建 thread 时,Worker 对象将自己作为 Runnable 的实现传入了 thread 中。线程池在执行时调用的 t.start(),实际上运行的是 t 所属 Worker类的run
方法。
run方法
worker 的 run
方法如下,
public void run() {
runWorker(this);
}
上面的代码中,runWorker
方法是外部类 ThreadPoolExecutor的方法,之后会讲解。
对Worker类的理解
- Worker类像是任务代理,是线程池中最小的执行单位,对线程池中的工作线程进行了良好的封装
- Worker 本身也实现了 AQS,所以其本身也是一个锁,其在执行任务的时候,会锁住自己,任务执行完成之后,会释放自己
- 在 Worker 初始化时
this.thread = getThreadFactory ().newThread (this)
这行代码比较关键。它把当前 Worker 作为线程的构造器入参,在后续的实现中会发现这样的代码:Thread t = w.thread;t.start()
,此时的 w 是 Worker 的引用,此处 t.start 实际上执行的就是 Worker 的 run 方法