Java 线程池详解
概述
Java 线程池 详解,主要是 基于 ThreadPoolExecutor 出发。
宏观上会详细解释 该类中,每一个组件的作用,以及工作流程,微观会包括线程池的 创建、运行、任务提交 以及生命周期的管理等。
各组件介绍
内部类
- Worker:负责管理线程执行任务时的中断控制状态,确保工作线程正在等待执行任务时,不会被意外中断。
- 拒绝策略
- CallerRunsPolicy:调用线程执行任务
- AbortPolicy:拒绝任务,抛出异常
- DiscardPolicy:丢弃任务,空实现
- DiscardOldestPolicy:丢弃最旧的任务,然后 重新 提交任务
属性
- ctl:AtomicInteger,是整个 线程池的 核心控制 变量,作用见备注。
- mainLock:ReentrantLock。保证 工作线程的访问是线程安全的,使用锁,而不是 并发 集合的原因,是因为 也可以 基于 锁去 计算一些统计信息;串行化 对
interruptIdleWorkers方法的调用,从而避免不必要的中断。- 对工作线程集合的访问锁:
- 在线程池中,工作线程集合(workers set)是用来存放正在执行任务的工作线程的数据结构。为了保证对工作线程集合的访问是线程安全的,需要使用锁来进行同步。
- 虽然可以使用某种并发集合(concurrent set),但通常更倾向于使用锁来管理工作线程集合的访问。其中一个原因是,这样可以串行化对**
interruptIdleWorkers方法的调用,从而避免不必要的中断风暴,尤其是在线程池关闭过程中。否则,退出的线程可能会同时中断尚未中断的线程。另外,这种方式也简化了一些相关的统计信息的维护,例如largestPoolSize**等。
- 在关闭过程中持有主锁:
- 在执行线程池的关闭操作时,通常也需要持有主锁(mainLock)。这是为了确保在检查是否有权限中断线程和实际中断线程之间,工作线程集合保持稳定。
- 在执行**
shutdown和shutdownNow**操作时,线程池会持有主锁。这样可以保证在分别检查是否有权限中断线程和实际中断线程之间,工作线程集合保持稳定。这种方式也有助于确保在线程池关闭过程中的正确性和可靠性。
- 对工作线程集合的访问锁:
- workers:HashSet,工作线程的集合。线程池所有的worker都在此处。访问次字段必须 拥有 mainLock。
- termination:Condition,对
awaitTermination的支持 - container:工作线程的容器
- completedTaskCount:完成任务数
- allowCoreThreadTimeOut:默认是
false。 如果为true ,则 核心线程也会 根据keepAliveTime在 空闲时 销毁。 - defaultHandler:默认的拒绝策略Handler:
AbortPolicy - shutdownPerm:在调用线程池的**
shutdown和shutdownNow**方法时,对调用者权限的要求,以及对工作线程集合进行中断操作时的安全性处理 - 线程池相关参数:
- largestPoolSize:记录线程池在运行过程中的最大并发线程数量
- maximumPoolSize:最大线程数
- workQueue:等待队列。加入 任务的时候,如果线程达到了最大核心 线程数,则会添加到此队列中
- threadFactory:线程工厂
- handler:拒绝策略Handler
- keepAliveTime:线程存活时间
- corePoolSize:核心线程数
CTL 是 一个 int 类型,占用 4个字节,32 为,其中 使用 29位 来存储 workerCount 的数量,所以能允许的 最大线程数 是 (2^29)-1(因为最高位是符号位,所以int 能表示的最大的 正数 是 (2^31)-1),使用了 29 位 来 表示 workerCount,剩下的 3 位 被看做 一个整体,用作 线程池状态控制。
runState 定义了线程池的生命周期,包括以下几个状态:
RUNNING -1:接受新的 任务,并处理队列中的任务SHUTDOWN 0:不接受 新任务,但处理队列中的任务STOP 1:不接受新任务,不处理队列中的任务,并中断正在执行的任务TIDYING 2:所有任务已经终止,线程池正在转换状态,执行**terminated()**钩子方法。TERMINATED 3:线程池已经终止,不再接受新任务,不处理队列中的任务。 -1、0、1、2、3 可以 理解为他们的状态值 runState 状态转换:
RUNNING->SHUTDOWN:调用**shutdown()**方法时。RUNNING或SHUTDOWN->STOP:调用**shutdownNow()**方法时。SHUTDOWN->TIDYING:当队列和线程池都为空时。STOP->TIDYING:当线程池为空时。TIDYING->TERMINATED:当**terminated()**钩子方法完成时。
内部详解
创建线程池
创建线程池 主要 有 7 个参数:
- corePoolSize:核心线程池数
- maximumPoolSize:最大线程池数
- keepAliveTime:存活时间
- unit:存活时间的 单位
- workQueue:等待队列
- threadFactory:线程工厂,默认使用:
Executors.*defaultThreadFactory*() - handler:拒绝策略
这每一个 参数的 作用 过程和作用方式,见 任务 提交分析。
任务提交
任务 提交分为了 两种:
- submit:提交任务,会返回 返回值
- execute:提交任务,没有返回值
submit方法
submit 方法 一共有三个 方法:
- Runnable:
- 返回值为 Null
- 参数新增 Result:返回值为 Result。
- Callable:返回值为 task 的返回值
Runnable 和 Callable 一样,Runnable 会在 内部 将 任务 封装成带 Future 返回值的任务。下面的额解析 就直接 以 Callable 参数为主。
提交任务的 过程主要有 2个 过程:
- 将 需要执行的 任务,进行封装,封装为
FutureTask任务,并放回这个Future的 信息 - 执行
execute方法,提交任务。该方法 即 后续的execute方法。此处 不展开。
execute方法
线程池提交任务的 核心方法。主要的过程如下:
- 通过 ctl ,判断 当前的 核心线程池数 是否 小于指定数量
- 小于 则 使用
addWorker方法,core参数为 true。
- 小于 则 使用
- 判断当前的 线程池状态是否是运行 状态
- 如果处于运行状态,则 添加到等待队列
- 如果 添加成功,会重新 检查运行状态
- 如果当前的工作线程数为 0 ,则会 调用
addWorker添加一个空任务
- 如果处于运行状态,则 添加到等待队列
- 如果线程池不处于运行状态,或者加入队列失败,则会重新尝试 调用
addWorker,如果失败,则 拒绝任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
addWorker 承担了 大部分的逻辑:
addWorker
此方法的主要作用是 判断 线程池中,线程的数量,在需要的时候创建新的 线程,并开启线程。
- 进入循环,循环中判断 线程池状态
- 如果当先线程池 状态 大于等于
SHUTDOWN状态(TERMINATED、TIDYING、STOP、SHUTDOWN),并且 满足 一下三点之一,则返回 false。(SHUTDOWN和后续状态不接受新任务)- 当前线程池状态 为
TERMINATED或者TIDYING或者STOP - 参数中的 task 信息不为空
- 等待队列为空
- 当前线程池状态 为
- 进入内循环
- 获取工作线程数,根据 core 参数判断,当前的 线程数 是否大于 核心线程数或者 最大线程数。 如果大于,则返回 false
- 增加 工作线程的 数量,更新 ctl 的值,退出外层循环
- 如果当先线程池 状态 大于等于
- 用当前的 任务 创建一个 新的 worker
- 设置当前worker 的状态
- 使用线程工厂创建一个线程
- 获取
mainLock - 判断当前线程池 状态,如果 线程池 正在运行 或者 线程池 处于
SHUTDOWN或者RUNNING状态并且当前任务为空- 将任务 添加到
workers列表中 - 更新
largestPoolSize的值
- 将任务 添加到
- 释放锁
- 如果 线程 已经成功添加到
workers中,则使用container开启线程(调用start方法) - 如果线程启动成功,则返回。如果线程启动失败,则尝试 终止线程池。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
// 如果是 调用了 **shutdown 方法**
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) // **shutdownNow()**
|| firstTask != null 任务不为空
|| workQueue.isEmpty())) 等待队列为空
return false; // 表明当前线程 在 SHUTDOWN 状态,则不接受 任何 新的 任务
for (;;) {
// 判断工作线程数 是否 达到了 预定 的线程数,
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK)) // COUNT_MASK 为 00011111111111111111111111111111
return false;
// 增加 工作线程数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 再次 检查 状态
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 新增 一个worker,会同时创建线程
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
// 如果 线程池正在运行中,或者 当前的额 任务是空的
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
// 添加 worker 到工作列表
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
// 开启线程
if (workerAdded) {
container.start(t);
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 处理失败
addWorkerFailed(w);
}
return workerStarted;
}
当执行 完成 container.start(t); 后,worker 中的 线程 就被 开启了, worker 中线程的创建方式如下getThreadFactory().newThread(this); worker 本身实现了 Runnable接口,所以 开启线程后,就会执行 worker 中的 run ——> runWorker(this); 方法。
runWorker方法:执行任务的核心
- 获取到 当前
worker的 第一个任务(即 提交任务时的 任务) - 清空 worker 中 所有的 锁(worker 自己实现的锁,直接释放 所有资源)
- 进入循环(当前任务 不为空,或者 从 等待队列中获取的任务不为空)
- 当前 worker 加锁
- 如果当前线程池状 大于等于
STOP,或者 当前线程被中断过并且线程池状态 大于等于STOP, 并且 当前线程没有发生中断,则触发 中断 - 执行
beforeExecute方法 - 执行任务的
- 执行
afterExecute方法 - 累加 当前worker 完成的 任务数
- 解锁 当前 worker
- 执行
processWorkerExit方法,做 线程退出后的后置处理- 获得
mainLock - 累加 完成任务数
- 从
workers中移除 该 worker - 解锁
- 获得
- 尝试 关闭线程池
- 如果 当前线程池 还在运行中
- 如果 是因为 异常 退出,则 新添加 一个 空的线程
- 如果是正常退出,则判定 当前 线程池中核心线程 的数量
- 如果 当前队列有任务,并且 线程数为空,则会 新增一个线程
getTask 方法 详解
getTask 方法的 作用是 从 等待队列中 获取任务信息。
- 进入循环
- 判断当前线程池状态,如果当前线程池状态包括
SHUTDOWN、STOP、TINDYING、TERMINATED并且 是STOP、TINDYING、TERMINATED或者 任务队列为空,则减少工作线程数,并返回(相当于终结线程)。 - 计算线程池数,如果当前线程数 可减少,则释放线程
- 从等待 队列中 获取 任务
- 如果配置了 最大线程 的存活时间,则 会用指定时间去获取任务,获取不到任务,线程终止
- 否则 阻塞 等待并返回 任务
- 判断当前线程池状态,如果当前线程池状态包括
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 超过了 最大线程池数 或者 配置了时间,并且 等待队列为空,则 减少线程
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 通过 超时 时间获取 任务,获取不到,则 线程被 释放
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
shutDown、terminal 等方法详解
shutDown 方法
shutDown 不会 立即 关闭线程池,会启动一个 有序 关停程序。具体流程如下:
- 获得
mainLock锁 - 检查 是否有关停权限
- 死循环中 修改 线程池状态为
SHUTDOWN - 调用
interruptIdleWorkers方法 ,中断空闲线程- 获取
mainLock - 从
workers开始遍历 所有 的 worker- 如果 线程 没有发生过中断,并且 获取 worker的 锁成功(说明 没有在执行任务,执行任务时,需要获取锁),则发出中断
- 释放
mainLock
- 获取
- 执行
onShutdown()回调钩子 - 释放
mainLock - 执行
tryTerminat()方法
shutDownNow()方法
和 shutDown 方法 不一样,该 方法 会立即 停止,并且 停止 接受 新的 任务,并且会中断正在执行的任务。流程如下:
- 获取
mainLock的锁 - 检查是否有权限 关停
- 将 线程池状态 设置为
STOP:不接受 新任务、不处理 队列中的任务,并且中断 正在执行的任务 - 调用
interruptWorkers方法,中断 所有的线程:- 调用
worker中的interruptIfStarted,只要线程是开启状态,并且 没有发生过中断,则发出中断。
- 调用
- 调用
drainQueye方法,清空所有正在等待的任务。 - 释放锁
- 返回 所有未执行的任务
tryTerminate() 方法
tryTerminate 方法 会尝试 将 线程池 状态 先变为 TIDYING ,然后 将状态变更为 TERMINATED 。具体流程如下(以下流程 均是在一个死循环中):
- 如果当前 线程池满足以下情况之一,则直接返回:
- 线程池正在运行
- 线程池 处于
TIDYING或者TERMINATED状态:说明正在被终止 - 处于
SHUTDOWNRUNNING状态,并且等待队列不为空: 说明 正在运行 或者 任务没有处理完
- 如果当前的工作线程数 大于0,则调用
interruptIdleWorkers中断第一个 worker - 获取
mainLocl - 更改 线程池 状态为
TIDYING,更新 成功则:- 调用
terminated方法:ThreadPoolExecutor中为空shi'xian - 设置线程池状态为
TERMINATED状态 - 通知 监听
termination的线程:awaitTermination()方法会监听 - 关闭
container
- 调用
总结
通过上诉分析,可以很 清楚 的知道,创建线程池时,每一个 参数的作用过程:
-
maximumPoolSize:最大线程数
-
workQueue:等待队列。加入 任务的时候,如果线程达到了最大核心 线程数,则会添加到此队列中
-
threadFactory:线程工厂
-
handler:拒绝策略Handler
-
keepAliveTime:线程存活时间,还有一个 unit 为单位
-
corePoolSize:核心线程数
-
提交任务时,如果不满足 核心线程数量,则创建线程:
corePoolSize -
如果 满足 核心线程数量,则 加入到等待队列:
workQueue -
加入等待 队列 失败,则 使用
addWorker方法 ,判定 是否满足最大 线程数:maximumPoolSize -
加入失败,则 使用 拒绝策略:
handler -
加入成功,则 开启线程,执行任务,执行完成后 通过
getTask方法 获取 队列中的任务:workQueue- 获取任务时,判断线程数量是否可减少
- 如果大于 最大线程数,则减少 线程:
maximumPoolSize - 如果当前线程数 大于 核心线程数,并且 获取任务 超时,则减少 线程:
corePoolSize、maximumPoolSize、keepAliveTime
- 如果大于 最大线程数,则减少 线程:
- 获取任务时,判断线程数量是否可减少
worker中 锁的作用
- 避免 被意外中断:
tryTerminate会尝试 中断 空闲线程需要申请 锁,worker 在执行任务时 会去 申请锁,通过锁的方式,保证 不会被意外中断。 - 通过 锁中的
state判断,线程是否启动(因为启动的时候会加锁,所以state的值 大于0)