简介
ThreadPoolExecutor线程池通过重用之前创建好的线程来处理任务,大大降低了线程频繁创建和销毁导致资源消耗。
常量
/**
* 初始化线程池的状态以及线程池的线程数量
* 初始后的ctl的二进制为1110 0000 0000 0000 0000 0000 0000 0000
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/**
* 使用32位二进制来表示线程池的状态和线程池中线程的数量
* 1110 0000 0000 0000 0000 0000 0000 0000 前3位为线程池的状态
* 0001 1111 1111 1111 1111 1111 1111 1111 后29位为线程池中线程的数量
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 线程池中最大的线程容量
* (1 << 29) -1
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/**
* 接受新的线程任务并处理任务队列中的线程任务
* 1110 0000 0000 0000 0000 0000 0000 0000
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* 不接受新的线程任务,但是能处理任务队列中的线程任务
* 并将线程池中的所有的线程都打上线程中断的标志
* 0000 0000 0000 0000 0000 0000 0000 0000
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* 不接受新的线程任务,也不接受处理任务队列中的线程任务
* 并将线程池中的所有的线程都打上线程中断的标志
* 0010 0000 0000 0000 0000 0000 0000 0000
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* 线程池中的所有的线程都已经被清理
* 0100 0000 0000 0000 0000 0000 0000 0000
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* 线程池已经终止
* 0110 0000 0000 0000 0000 0000 0000 0000
*/
private static final int TERMINATED = 3 << COUNT_BITS;
/**
* 线程任务队列,当线程池中的线程执行完任务之后则会从任务队列中获取任务继续执行
*/
private final BlockingQueue<Runnable> workQueue;
/**
* 重入锁,防止多线程同时对线程集合操作
*/
private final ReentrantLock mainLock = new ReentrantLock();
/**
* 线程池
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
* Wait condition to support awaitTermination
*/
private final Condition termination = mainLock.newCondition();
/**
* 线程池中线程到达的最大的容量大小
*/
private int largestPoolSize;
/**
* 全局的线程任务计数器
* 用来记录所有线程执行任务次数的计数器
*/
private long completedTaskCount;
/**
* 线程工厂
* 根据该线程工厂来创建执行任务的线程
* 可以使用默认的线程工厂
* 也可以自己实现ThreadFactory接口并且重写newThread方法
*/
private volatile ThreadFactory threadFactory;
/**
* 线程任务执行失败时所执行的拒绝策略处理器
* 可以使用默认的拒绝策略处理器
* 也可以自己实现RejectedExecutionHandler接口并且重写rejectedExecution方法
*/
private volatile RejectedExecutionHandler handler;
/**
* 线程池中非核心的线程执行完任务之后
* 如果超过该指定的时间没有获取到线程任务
* 则会释放掉自己
*/
private volatile long keepAliveTime;
/**
* 默认为false
* 当参数为false的时候,核心线程不受keepAliveTime参数影响
* 只要没有获取到线程任务则可以一直等待
* 当参数为true的时候,核心线程在执行任务之后
* 如果超过keepAliveTime指定的时间没有获取到线程任务,则会释放掉自己
*/
private volatile boolean allowCoreThreadTimeOut;
/**
* 线程池中执行任务的核心的线程数量
* 当corePoolSize为0时则会根据maximumPoolSize来创建非核心的线程
* 当非核心的线程超过keepAliveTime指定的时间没有执行任务了则会释放掉自己
*/
private volatile int corePoolSize;
/**
* 指定的线程池中最大的线程数量
* corePoolSize指定的数量的线程为核心线程数
* maximumPoolSize - corePoolSize = 非核心的线程数
*/
private volatile int maximumPoolSize;
/**
* 默认的拒绝策略处理器
*/
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
/**
* 中断一个工作线程
*/
private static final boolean ONLY_ONE = true;
-
ctl:ctl是一个原子类型的值,用于区别线程池的状态以及线程池中的线程数量,在初始化后的ctl的二进制为1110 0000 0000 0000 0000 0000 0000 0000,而前3位二进制111代表线程池的状态,而后29位代表线程池中的线程数量。 -
COUNT_BITS:该值可以理解为线程池的状态和线程池中线程的数量在二进制中开始区分的位置。 -
CAPACITY:线程池中能容纳最大的线程数量,该值并不是在创建的时候指定的最大的容量,而是因为线程池中的线程数量是用29位二进制来表示的,而该值则是29位二进制中所能表示的最大值。 -
RUNNING:该值说明线程池正在运行状态,线程池在该状态下会接受新的线程任务并处理任务队列中的线程任务,该值用二进制表示为 1110 0000 0000 0000 0000 0000 0000 0000。 -
SHUTDOWN:该值说明线程池正在关闭状态,线程池在该状态下不会接受新的线程任务,但是会处理任务队列中的线程任务,并且会将线程池中所有的线程都打上中断标志,当被打上中断标志的线程没有获取到线程任务时则会被释放掉,获取到线程任务的线程则会将线程任务执行完毕,直到任务队列中没有了线程任务之后则会释放掉,该值用二进制表示为 0000 0000 0000 0000 0000 0000 0000 0000。 -
STOP:该值说明线程池正在关闭状态,线程池在该状态下是不会接受新的线程任务,也不会去处理任务队列中的线程任务,并会将线程池中所有的线程打上中断标志,将线程池中的所有的线程都释放掉,该值用二进制表示为 0010 0000 0000 0000 0000 0000 0000 0000。 -
TIDYING:该值说明线程池是已经关闭的状态了,并且已经将线程池中的所有的线程都已经起来了,该值用二进制表示为 0100 0000 0000 0000 0000 0000 0000 0000。 -
TERMINATED:该值说明线程池已经是终止状态了,线程池已经是完完全全的被关闭了。 -
workQueue:线程任务队列,当线程池中的线程执行不过来的时候,有新的线程任务来时则会添加到任务队列中,可以根据自己的需求选择不同的队列来实现不同的效果。 -
mainLock:重入锁,也可以说是独占锁,防止多线程同时对线程池进行操作。 -
workers:线程池,其实就是用一个HashSet集合来存放线程。 -
termination:该对象是一个Condition类型的,主要的作用在于awaitTermination方法中,等待线程池终止。 -
largestPoolSize:线程池中的线程数量已经到达过的最大大小。 -
completedTaskCount:全局线程任务计数器,用来记录所有线程执行任务的次数。 -
threadFactory:线程工厂,根据该工厂来创建执行任务的线程,可以使用默认的线程工厂,也可以自己实现ThreadFactory接口并且重写newThread方法。 -
handler:线程任务执行失败时所执行的拒绝处理处理器,可以使用默认的拒绝策略处理器,也可以自己实现RejectedExecutionHandler接口并且重写rejectedExecution方法。 -
ThreadPoolExecutor中提供了四种拒绝策略处理器,AbortPolicy是默认的拒绝策略处理器,当线程任务被拒绝执行时则会丢掉该线程任务并且抛出异常,DiscardPolicy处理器则是直接丢掉该线程任务并且不会抛出任何异常,DiscardOldestPolicy处理器则是将任务队列中的头线程任务移除掉,并将当前线程任务添加到任务队列中去,CallerRunsPolicy处理器则是会使用调用者线程去执行该线程任务,并不会等待线程池中的线程去执行。 -
keepAliveTime:线程池中非核心线程执行完线程任务之后,如果超过该指定的时候没有获取到线程任务,则会释放掉当前线程。 -
allowCoreThreadTimeOut:该参数默认为false,当参数为false的时候,核心线程不受keepAliveTime参数影响,没有获取到线程任务则会一直阻塞等待,当参数为true的时候,核心线程在执行任务之后,如果超过keepAliveTime指定的时间没有获取到线程任务,则会释放掉当前线程。 -
corePoolSize:线程池中执行任务的核心线程数量,当corePoolSize为0时则会根据maximumPoolSize来创建非核心线程,当非核心线程超过keepAliveTime指定的时间没有获取到线程任务则会释放掉当前线程。 -
maximumPoolSize:指定的线程池中最大的线程数量,corePoolSize指定的数量的线程为核心线程数,maximumPoolSize - corePoolSize = 非核心的线程数。 -
defaultHandler:默认的拒绝策略处理器,就是AbortPolicy拒绝策略处理器。 -
ONLY_ONE:在关闭线程池的时候,通过该参数来决定是中断所有的线程,还是中断一个线程。
线程池的生命周期一共分为4种,而SHUTDOWN与STOP是同一周期的,只不过线程池在SHUTDOWN期间是可以处理任务队列中的线程任务的,而STOP是不能处理任务队列中的线程任务的。
构造方法
/**
* 根据指定的参数以及指定的拒绝策略处理器和线程工厂来创建线程池
*/
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;
}
ThreadPoolExecutor中一共有4个构造方法,其它3个构造方法都是调用的上面代码片段中的构造方法,而上面代码片段的构造方法只是初始化一些线程池的参数。
ThreadPoolExecutor中最核心的方法就是execute和shutdown方法,而execute方法最为复杂,我们一步一步的分析这两个方法。
execute
/**
* 传入线程任务,该线程任务在某一时间执行
* 执行任务的线程可以是线程池中的线程,也可以是新创建的线程
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取线程池的状态以及线程池中的线程数量
int c = ctl.get();
// 通过workerCountOf方法计算出当前线程池中的线程数量
// 并比较线程池中的线程数量是否小于指定的核心线程数量
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);
// 防止在创建ThreadPoolExecutor的时候传递的corePoolSize为0
// 导致不能创建核心的线程,也就造成线程池中没有线程
// 当线程池中没有线程的时候,线程任务添加到了任务队列中
// 造成了任务队列中的线程任务没有线程去执行
// 所以在这里就会创建一个非核心的线程来执行任务队列中的线程任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果isRunning为false的话说明线程池已经关闭,此时就不能创建非核心线程来执行任务,而是执行拒绝策略
// 当isRunning为true,但是任务队列中的容量已经到达最大,此时将线程任务添加到任务队列中则会失败
// 此时就会去尝试创建非核心的线程来执行线程任务
else if (!addWorker(command, false))
reject(command);
}
ctl是被AtomicInteger类型修饰的,通过ctl.get()方法获取到当前线程池的状态以及线程池中的线程数量,而线程池的状态和线程池中的线程数量是用一个值来存放的,此时就需要通过workerCountOf方法计算出线程池中线程的数量,当线程池中的线程数量小于corePoolSize,则说明线程池中的核心的线程数量还没有到达corePoolSize指定的线程数,此时就需要通过addWorker方法来创建线程池中的核心线程,当创建核心线程并执行线程任务成功时则会return,如果失败则会获取最新的线程池状态以及线程池中线程的数量,后续会根据情况来决定是创建非核心线程还是将线程任务添加到任务队列中,等待其它线程来执行。
我们先看一下workerCountOf和isRunning方法,再来分析后续的操作。
private static int workerCountOf(int c) {
return c & CAPACITY;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
-
workerCountOf方法中的CAPACITY则是当前线程池中最大的线程容量,用二进制表示为0001 1111 1111 1111 1111 1111 1111 1111,而c则是当前线程池的状态和线程池中线程数量共用的值,假设线程池的状态为RUNNING状态,而线程池中线程的数量为1,那么c用二进制表示为1110 0000 0000 0000 0000 0000 0000 0001,此时两个二进制进行与运算之后的二进制为0000 0000 0000 0000 0000 0000 0000 0001,转换成十进制为1,从而获取到了线程池中线程的数量为1。 -
isRunning方法就比较简单,当线程池是RUNNING状态的时候,此时的c则是一个非常大的负数,而SHUTDOWN为0,c自然就比SHUTDOWN小。
我们继续来看execute方法中的代码,当线程池中的核心线程数量超出指定的参数corePoolSize或创建核心线程的时候失败,那就会走到当前分支isRunning(c) && workQueue.offer(command),当线程池在RUNNING状态下则会尝试将线程任务添加到任务队列中,添加到任务队列中成功之后则获取最新的线程池状态,如果说最新的线程池状态不是RUNNING状态了,说明在添加线程任务到任务队列的时候,线程池的状态发生了变化,此时就需要将刚添加到任务队列中的线程任务从队列中移除并执行拒绝策略方法。
当线程池在RUNNING状态下,那就要执行workCountOf(check) == 0分支来校验线程池中是否有线程,如果没有线程则需要通过addWorker方法来创建一个非核心的线程来执行刚才添加到任务队列中的线程任务。
为什么这里需要要校验线程池中的线程数量呢?
因为在创建ThreadPoolExecutor的时候可能指定的corePoolSize为0,导致不能创建核心的线程,也就造成了线程池中没有线程,当线程池中没有线程的时候,线程任务添加到任务队列中,也就没有线程去执行任务队列中的线程任务,所以这里就需要校验来决定是否创建非核心的线程。
我们再来看最后的一个分支! addWorker(command, false),该分支只有在线程池不为RUNNING状态或添加线程任务到任务队列中失败时才会执行,如果说线程池不为RUNNING状态的话说明线程池已经关闭了,前面也说过线程池在关闭状态下是不接受新的线程任务的,所以会执行拒绝策略方法,如果是添加线程任务到任务队列中失败说明任务队列中的线程任务已经到达最大容量,此时就需要创建非核心线程来协助核心线程处理线程任务。
addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:for (;;) {
// 获取线程池的状态以及线程池中的线程数量
int c = ctl.get();
// 获取线程池的状态
int rs = runStateOf(c);
// 如果说线程池已经是SHUTDOWN状态了,那就不会再接受新的线程任务了,但是任务队列中还有没被处理的线程任务
// 此时就需要创建新的线程来执行任务队列中的线程任务
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
// 获取线程池中的线程数量
int wc = workerCountOf(c);
/**
* wc >= CAPACITY 如果说当前线程池中的线程数量已经大于等于线程池中线程最大的容量时则不会继续创建线程
* 如果说线程池中的线程数量并没有到达最大的容量时,则会根据core来决定是否需要继续创建线程
* 如果core为true则说明当前要创建的线程是核心线程,则需要比较线程池中的线程数量是否到达了核心线程的数量
* 没有到达核心的线程数量的话则会创建核心的线程,如果到达了核心的线程数量的话则会尝试将线程任务添加到任务队列中
* 如果任务队列的容量已经到达了最大容量则会将core设置为false
* 当core为false的时候则会比较线程池中的线程数量是否大于等于指定的线程池最大的容量
* 如果没有到达指定的线程池最大容量则会创建非核心的线程来执行任务,非核心的线程在一定的时间内没有执行线程任务的话则会被释放掉
* 如果已经到达了指定的线程池最大容量则会执行拒绝策略,拒绝执行该线程任务
*/
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 通过cas操作自增线程池中的线程数量
if (compareAndIncrementWorkerCount(c))
// 更新线程池中的线程数量成功,退出循环执行后续操作来创建线程并执行线程任务
break retry;
// cas执行失败,可能有多个线程在执行cas操作
// 此时就需要获取最新的线程数量,重新执行内部循环来校验是否需要创建线程
c = ctl.get();
if (runStateOf(c) != rs)
// 最新的线程池状态与之前获取到的线程池状态不一样
// 此时就需要重新执行外部循环来决定是否继续创建线程
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建线程并设置线程要运行的初始任务
w = new Worker(firstTask);
// 获取执行任务的线程
final Thread t = w.thread;
if (t != null) {
// 获取锁,防止在多线程下同时对workers操作
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// 获取线程池最新的状态
int rs = runStateOf(ctl.get());
/**
* 当线程池的状态为RUNNING的时候是会正常的执行线程任务的
* 当线程池的状态为SHUTDOWN的时候分为两种情况:
* 1.firstTask不为空的意思就是说要添加新的线程任务,在这种情况下是不能接受新的任务的
* 2.firstTask为空的意思是不是添加新的线程任务,但是任务队列中有线程任务没有被执行,而线程池中也没有了线程
* 此时就需要创建新的线程来将任务队列中的线程任务进行执行
*/
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
// 将工作的线程添加到线程池中
workers.add(w);
// 获取线程池中线程的数量
int s = workers.size();
if (s > largestPoolSize)
// 更新线程池中线程到达的最大的容量大小
largestPoolSize = s;
// 设置添加线程到线程池中成功的标记
workerAdded = true;
}
} finally {
// 释放锁
mainLock.unlock();
}
if (workerAdded) {
// 启动线程任务
t.start();
// 设置启动线程任务成功的标记
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 线程任务执行失败
addWorkerFailed(w);
}
return workerStarted;
}
addWorker方法主要是创建线程并执行线程任务,我们先看addWorker方法中的两个参数,firstTask是创建完线程会所需要执行的任务,core则是代表当前创建的线程是否是核心线程,addWorker方法分为两部分,第一部分则是校验是否需要创建线程,第二部分则是创建线程并执行线程任务,我们先看第一部分的代码。
第一部分的代码被for循环包裹着,先通过runStateOf方法来获取当前线程池的状态,再看后续的if语句,当线程池的状态大于等于SHUTDOWN状态时,就会校验线程池的状态是否是SHUTDOWN状态,如果说线程池的状态是SHUTDOWN状态时,那就会校验firstTask是否为空,如果firstTask不为空的话,则说明当前要执行一个新的线程任务,但是在SHUTDOWN状态下是不能接受新的线程任务的,此时就会返回false并执行拒绝策略方法,如果说firstTask为空,那就要校验任务队列中的线程任务是否为空,任务队列不为空的情况下就会创建一个线程来执行任务队列中的线程任务,如果任务队列为空则返回false并执行拒绝策略方法,如果说线程池的状态为STOP状态的时候则会直接返回false并执行拒绝策略方法,因为在STOP状态下是不能接受新的线程任务并且也不能处理任务队列中的线程任务的。
再看if语句后面的for循环代码,先通过workerCountOf方法获取到当前线程池中的线程数量,通过wc >= CAPACITY来比较当前线程池中的线程数量是否已经到达最大的线程数量,在正常情况下该条件是不成立的,因为CAPACITY所代表的数为(1 << 29) -1,足足有5亿多,再看后一个条件语句wc >= (core ? corePoolSize : maximumPoolSize),该条件会根据你传递进来的参数core来决定是否需要继续创建线程。
在core为true的情况下,说明当前要创建的是核心线程,那就需要用当前线程池中线程的数量与指定的核心线程数corePoolSize进行比较,如果线程池中的线程数量已经超过了指定的核心线程数,那就不能继续创建核心的线程。
当core为false的时候,说明当前要创建非核心的线程,那就需要用线程池中的线程数量与指定线程池中线程最大的数量进行比较,如果没有到达指定的线程池最大容量则会创建非核心的线程来执行任务,非核心的线程在一定的时间内没有执行线程任务的话则会被释放掉,如果已经到达了指定的线程池最大容量则会执行拒绝策略,拒绝执行该线程任务。
当上述条件都通过了则说明需要创建线程,此时就会通过cas操作尝试将线程池中的线程数量加1,cas操作成功之后会通过外层for的retry标签退出循环,并执行后续的创建线程以及执行线程任务的操作,如果cas失败则说明有其它线程正在对线程池中线程数量进行操作,此时就需要获取最新的线程池状态以及线程数量,并重新执行for循环来决定是否继续创建线程。
Worker
Worker(Runnable firstTask) {
// 加锁,防止线程被中断
setState(-1);
// 线程执行的任务
this.firstTask = firstTask;
// 使用线程工厂创建线程
this.thread = getThreadFactory().newThread(this);
}
在Worker类中继承了AQS也实现了Runnable并重写了run()方法,在构造方法中创建线程的方法就是使用线程工厂来创建的。
我们此时来看第二部分的代码,首先会通过new Worker来创建线程以及绑定线程需要执行的线程任务,再通过w.thread来获取刚刚创建好的线程,在后续中为什么要通过ReetrantLock来加锁呢?因为在后续中需要将创建好的线程添加到线程池中,而线程池是用HashSet来表示的,HashSet本就是线程不安全的集合,所以在这里加锁的原因就是防止多线程同时对线程池HashSet进行操作。
我们再来看if语句rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null),当线程池状态rs小于SHUTDOWN的话,那就说明当前线程池的状态为RUNNING状态,此时可以正常的创建线程并执行线程任务。
当线程池的状态为SHUTDOWN状态的时候分为两种情况:
firstTask线程任务为空说明当前不是添加新的线程任务,但是任务队列中有线程任务没有被执行,并且线程池中也没有了线程,此时就需要创建新的线程来将任务队列中的线程任务执行。firstTask线程任务不为空说明当前是添加新的线程任务,在SHUTDOWN状态下是不能接受新的线程任务的,此时就需要返回false并执行拒绝策略方法。
当上述条件通过了之后,则会调用workers.add方法将创建好的线程添加到线程池中去,然后获取线程池中最新的线程数量,并更新线程池中线程到达的最大容量大小largestPoolSize并设置添加线程到线程池中成功的标记,largestPoolSize记录着线程池中线程数量已经到达过的最大值。
当workerAdded标记为true时则会启动new Worker中创建的线程,此时就会执行Worker中的run方法,为什么是执行Worker中的run方法,而不是执行线程任务中的run方法呢?因为在new Worker中通过线程工厂来创建线程的时候传递了一个Runnable参数,而这个Runnable参数正是当前的Worker,而Worker又实现了Runnable接口并重写了run方法,所以启动线程的时候自然而然的就会运行Worker中的run方法了。
如果在将线程添加到线程池中失败或启动Worker线程失败的时候,则会执行addWorkerFaild方法。
addWorkerFaild
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
if (w != null)
// 从线程池中移除该线程
workers.remove(w);
// 线程池中的线程数量自减
decrementWorkerCount();
// 尝试结束线程池
tryTerminate();
} finally {
// 释放锁
mainLock.unlock();
}
}
addWorkerFailed方法会将Worker线程从线程池中移除,并将线程池中的线程数量减1,再通过tryTerminate方法来尝试结束线程池,tryTerminate方法我们在后面再讲。
run
public void run() {
runWorker(this);
}
在addWorker方法中将线程启动之后则会调用当前run方法,最终会在当前run方法中的runWorker方法中执行线程任务以及从任务队列中获取新的线程任务来执行。
runWorker
final void runWorker(Worker w) {
// 获取当前线程,该线程就是执行线程任务的线程
Thread wt = Thread.currentThread();
// 获取线程任务
Runnable task = w.firstTask;
w.firstTask = null;
/**
* 释放锁,为什么要释放锁?
* 因为在new Worker的时候为Worker添加了锁
* 当Worker持有锁的时候是不能被中断的
* 此处释放锁是为了让Worker允许被中断
* 此处释放锁了之后,如果说Worker被中断了,还是会继续执行完本次Worker中的线程任务
* 只有将本次的线程任务完成之后,通过getTask()方法从任务队列中去获取线程任务的时候
* 就会发现该线程已经被打上了中断的标志,此时该线程就不能继续获取线程任务来执行
* 而是将该线程从线程池中移除
*/
w.unlock();
boolean completedAbruptly = true;
try {
/**
* task != null 则说明当前线程是第一次执行线程任务
* 当线程任务执行完成之后则会从任务队列中获取线程任务来执行
* 如果任务队列中没有线程任务则会进行阻塞,直到任务队列中有新的线程任务
* 如果在阻塞期间线程被中断则不会去获取新的线程任务
* 而是退出阻塞等待并在后续将中断的线程从线程池中移除
*/
while (task != null || (task = getTask()) != null) {
// 加锁
w.lock();
// 当线程池的状态为STOP状态的时候则需要将未被打上中断的标志的线程打上中断标志
// 线程池已经停止,而线程池中的线程就不需要再继续执行线程任务了
// 被打上了中断标志的线程会执行完本次线程任务之后,不再执行后续的线程任务
if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
try {
// 线程任务执行之前要执行的操作
// 在ThreadPoolExecutor中并没有具体的逻辑代码
// 需要自己继承ThreadPoolExecutor并重写beforeExecute方法
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 执行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 线程任务执行完之后要执行的操作
// 该操作与beforeExecute一样并没有具体的逻辑代码
afterExecute(task, thrown);
}
} finally {
// 将线程任务置空,方便下次从任务队列中获取
task = null;
// 线程任务计数器
w.completedTasks++;
// 释放锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 当线程被中断或获取线程任务超时或者执行线程任务出现异常时则会执行
// 执行线程任务异常时completedAbruptly为true
// 当线程被中断时completedAbruptly为false
// 当线程获取线程任务超时completedAbruptly为false
processWorkerExit(w, completedAbruptly);
}
}
runWorker方法中主要代码就是while循环中的代码,我们直接从while循环代码来分析,当task不为null的时候,说明当前是一个新的线程任务,那将会通过if语句来校验是否需要将当前线程打上中断标志,防止线程池已经被关闭了,而任务队列中没有了线程任务,导致下一次从任务队列中获取线程任务的时候一直阻塞,从而导致线程没有被释放掉。
在执行task.run方法的前后会执行beforeExecute和afterExecute,beforeExecute和afterExecute在ThreadPoolExecutor中并没有具体的逻辑代码,需要自己继承ThreadPoolExecutor并重写这两个方法,而task是直接调用的run方法,将该线程任务看作是一个普通的方法来执行的,当执行完线程任务之后会将task置为空,方便下一次从任务队列中获取线程任务,completedTasks则是记录着当前线程已经执行过的线程任务的数量。
当在上面执行完线程任务的时候,会将task置为空,此时就会继续执行while语句来校验,此时就会发现task为空,那就通过getTask方法从任务队列中获取线程任务。
getTask
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
// 获取最新的线程池状态以及线程数量
int c = ctl.get();
// 获取线程池的运行状态
int rs = runStateOf(c);
// 当线程池被关闭了,则需要查看一下任务队列中是否还有任务没有被执行
// 如果任务队列中还有任务没有被执行的话,当前线程则会去执行
// 直到任务队列中没有了任务,当前线程才会从线程池中移除
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 线程池中的线程数量自减
decrementWorkerCount();
return null;
}
// 获取线程池中的线程数量
int wc = workerCountOf(c);
/**
* 当allowCoreThreadTimeOut为true的时候说明线程池中的所有的线程都会被淘汰
* 默认情况下为false,如果为false的时候则会比较线程池中的线程数量是否已经超出了核心的线程数量
* 如果超出核心的线程数量的话则会将超出的数量的线程淘汰
* timed = true 需要淘汰线程池中的线程
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 当线程池中的线程数量已经超出指定的线程池中最大的线程数量并且任务队列中没有了线程任务
// 此时就需要将当前线程释放掉并从线程池中移除
// 当上一次从任务队列中没有获取到线程任务并且此时需要淘汰线程的时候则会将当前线程释放掉并从线程池中移除
/**
* wc > maximumPoolSize 线程池中的线程数量是否超出了指定的线程池最大的数量
* (timed && timedOut) 上一次线程从任务队列中获取任务的时候是否超时并且此时需要淘汰线程池中的线程
*
* (wc > 1 || workQueue.isEmpty()) 当wc大于1则需要将超出的线程的数量或者超时的线程从线程池中移除
* wc小于等于1的时候是防止workQueue任务队列中有线程任务,从而没有线程来执行
*/
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
// 线程池中的线程数量自减
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 根据timed来决定是调用poll方法还是take方法来获取线程任务
// poll方法超过keepAliveTime指定的时间没有获取到线程任务,则会返回并将当前线程从线程池中移除并释放掉
// take方法则会一直阻塞,直到获取到了线程任务
// 当前两个方法都可以被中断,当被中断的线程则会从线程池中移除并释放掉
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null)
return r;
// 获取任务超时
timedOut = true;
} catch (InterruptedException retry) {
// 线程被中断,被中断的线程会清除掉中断的标志
timedOut = false;
}
}
}
getTask方法主要是从任务队列中获取线程任务,我们先看rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())语句,rs为当前线程池状态,当线程池的状态是大于等于SHUTDOWN状态的,此时就需要校验一下是SHUTDOWN状态还是STOP状态呢?如果说是STOP状态的话,那就直接退出并将线程从线程池中移除,因为在STOP状态下是不允许处理任务队列中的线程任务的,如果在SHUTDOWN状态下那就需要看一下任务队列是否为空,当任务队列不为空的情况下的话,那当前线程就需要去处理任务队列中的线程任务了。
allowCoreThreadTimeOut || wc > corePoolSize语句来决定是否需要释放掉一些线程,默认情况下allowCoreThreadTimeOut为false,在false的情况下就需要校验一下当前线程池中的线程数量是否超出了指定的核心线程数量corePoolSize,如果超出了corePoolSize的线程数量在后续长时间没有获取到线程任务的话则会被释放掉,当allowCoreThreadTimeOut为true的情况下时,如果线程池中的所有的线程长时间没有获取到线程任务的话就会被释放掉。
我们再看( wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())语句,该语句主要是将线程池中一些需要释放掉的线程从getTask方法中退出,我们先分析一下在哪些情况下会退出。
wc > maximumPoolSize && wc > 1:如果说线程池中的线程数量已经超过指定的线程池最大的线程数量时,就会将超出的数量的线程释放掉,但是需要预留一个线程,防止任务队列中的线程任务没有被执行完。wc > maximumPoolSize && workQueue.isEmpty():如果说线程池中的线程数量已经超过指定的线程池最大的线程数量并且线程池中的线程数量是小于等于1的,并且任务队列中没有了线程任务,就会将超出的数量的线程释放掉。(timed && timedOut) && wc > 1:如果说线程池中所有的线程在长时间没有获取到线程任务的时候都会被释放掉的时候,并且当前线程上一次从任务队列中获取线程任务的时候已经超时了,并且线程池中的线程数量是大于1的,此时就需要将当前线程释放掉。(timed && timedOut) && workQueue.isEmpty():如果说线程池中所有的线程在长时间没有获取到线程任务的时候都会被释放掉的时候,并且当前线程上一次从任务队列中获取线程任务的时候已经超时了,并且线程池中的线程数量是小于等于1的,并且任务队列中已经没有了线程任务,此时就需要将当前线程释放掉。
再看后续从任务队列中获取线程任务的代码,根据timed来决定是调用poll还是take,poll方法超过指定的时间没有获取到线程任务,则会返回并将当前线程从线程池中移除并释放,take方法则会一直阻塞,直到获取到了线程任务,当前两个方法都可以被中断,当被中断的线程则会从线程池中移除并释放掉,如果线程在poll和take方法中被中断了的话,没有立即被释放掉的话,下次再执行poll或take方法的时候,是不会被中断的,因为中断标志在抛出了InterruptedException异常时会被清除。
当线程需要从线程池中移除时,以及线程池中的线程都被移除了之后,线程池该如何操作呢?此时我们就需要看一下runWorker中所调用的processWorkerExit方法。
processWorkerExit
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly)
// 执行线程任务时出现异常,此时就需要将异常的线程从线程池中异常并将线程池中的线程数量减1
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// 将当前线程中执行的任务次数累加到全局的任务次数中
completedTaskCount += w.completedTasks;
// 将线程从线程池中移除
workers.remove(w);
} finally {
// 释放锁
mainLock.unlock();
}
// 尝试结束线程池
tryTerminate();
// 获取最新的线程池状态以及线程数量
int c = ctl.get();
/**
* 当线程在执行线程任务的时候出现了异常状态
* 则会尝试创建新的线程放入到线程池中,使用新的线程来替换出现异常的线程
*
* 如果是线程被中断了,那就说明线程池被关闭了
* 线程池被关闭了,并且线程池中没有线程,此时任务队列中的线程任务没有了线程去执行
* 就会去创建一个新的线程去执行任务队列中的线程任务,执行完任务队列中的线程任务之后线程则会被释放掉
*
* 如果线程在阻塞获取任务队列中的线程任务的时候超时了,那就要将该线程释放掉,超时的线程只有是非核心线程
* 非核心线程等待一定的时间没有获取到线程任务,那就要释放掉
*/
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
首先会将当前线程执行的线程任务次数累加到全局变量completedTaskCount中,再将线程从线程池中移除,然后调用tryTerminate方法尝试结束线程池,在最后的if语句中,如果说当前线程池的状态是SHUTDOWN状态或RUNNING状态下,当前线程出现了异常的话,就要通过addWorker方法尝试来创建新的线程并放入到线程池中,使用新的线程来替换出现异常的线程,如果说当前线程是被中断或获取线程任务超时时,此时就需要根据线程池中的线程数量以及任务队列中是否还有线程任务来决定是否需要创建线程。
- 当
allowCoreThreadTimeOut为true的时候,那min等于0,min等于0的情况下只有在线程池中没有线程并且任务队列中还有线程任务没有被执行的时候才会创建新的线程。 - 当
allowCoreThreadTimeOut为false的时候,那min等于corePoolSize,此时就不会执行min == 0 && ! workQueue.isEmpty()语句,则会执行workerCountOf( c ) >=min语句来校验当前线程池中的线程是否超出了核心线程数量,如果超出了那就需要释放掉当前线程,如果没有超出则会调用addWorker方法来决定是否需要创建线程,在addWorker方法中如果当前线程池的状态是SHUTDOWN状态并且workQueue任务队列中没有线程任务,此时并不会创建新的线程,只有在线程池在RUNNING状态下,或者说SHUTDOWN状态下并且workQueue任务队列中有线程任务才会创建新的线程。
tryTerminate
final void tryTerminate() {
for (;;) {
// 获取最新的线程池状态以及线程数量
int c = ctl.get();
/**
* isRunning(c) 当线程池的状态为RUNNING状态的时候说明线程池在正常运行并不需要清理线程池
*
* runStateAtLeast(c, TIDYING) 当线程池的状态为TIDYING整理状态或TERMINATED终止状态
* 说明线程池已经清理过了,则当前线程不需要重复清理
*
* (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()) 当线程池的状态为SHUTDOWN状态以及任务队列中的线程任务没有被执行完
* 那么当前线程则不能清理线程池,要等待任务队列中的线程任务被执行完
*
* 只有当线程池的状态为SHUTDOWN状态并且任务队列中没有了线程任务才会进行清理线程池
*/
if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
/**
* 线程池的状态为SHUTDOWN状态并且任务队列中没有了线程任务,此时需要校验线程池中的线程数量是否不为0
* 如果不为0则说明有许多空闲线程在阻塞,此时就需要调用interruptIdleWorkers方法将阻塞的头线程中断
* 阻塞的头线程被中断之后会从线程池中移除,当前阻塞的头线程执行到当前代码,
* 如果还是有许多的线程在阻塞,那就继续中断阻塞的头线程,直到线程池中所有的阻塞线程都被清理
*
* 当workerCountOf(c)等于0的时候则说明当前线程是线程池中的最后一个阻塞的线程
* 而最后一个线程就不用继续去中断阻塞的线程了,因为已经没有了阻塞的线程了
* 那最后一个线程就需要去将线程池的状态进行更改
*/
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 线程池中的最后一个线程来将线程池的状态更改为TIDYING状态
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// terminated方法没有具体的逻辑代码
terminated();
} finally {
// 将线程池的状态更改为TERMINATED状态
ctl.set(ctlOf(TERMINATED, 0));
// 唤醒在awaitTermination方法等待的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
tryTerminate方法中主要是清理线程池中的线程以及修改线程池的状态。
在第一个if语句主要是校验在哪些情况下需要继续清理线程池中的线程以及修改线程池的状态的。
isRunnung( c ):当线程池的状态为RUNNING状态的时候说明线程池正在正常运行并不需要清理线程池。runStateAtLeast(c, TIDYING):当线程池的状态为TIDYING整理状态或TERMINATED终止状态,说明线程池中的线程已经被清理过了,则当前线程不需要重复清理。(runStateOf( c ) == SHUTDOWN && ! workQueue.isEmpty()):当线程池的状态为SHUTDOWN状态以及任务队列中的线程任务没有被执行完时,那么当前线程则不能清理线程池,要等待任务队列中的线程任务被执行完。
当线程池的状态为SHUTDOWN状态并且任务队列中没有了线程任务,就会执行到workerCountOf( c ) != 0语句,此时就需要校验线程池中的线程数量是否不为0,如果不为0则说明有许多空闲线程在阻塞,此时就需要调用interrupIdleWorkers方法将阻塞的头线程中断,阻塞的头线程被中断之后会从线程池中移除,当阻塞的头线程执行到了当前代码的时候,如果还是有许多的线程阻塞,那就继续中断阻塞的头线程,直到线程池中所有的阻塞线程都被清理。
当workerCountOf( c )等于0的时候则说明当前线程是线程池中的最后一个阻塞的线程,而最后一个线程就不用继续去中断阻塞的下层了,因为线程池中已经没有了阻塞的线程了,那最后一个线程就需要将线程池的状态进行更改。
后续会通过cas操作将线程池的状态修改成TIDYING状态,然后再执行terminated方法,在ThreadPoolExecutor中terminated方法并没有具体的逻辑代码,需要自己实现,执行完terminated方法之后则会将线程池的状态修改成TERMINATED终止状态,然后唤醒可能在awaitTermination方法等待的线程。