什么是线程池
- 为了避免系统频繁的创建和销毁线程,我们可以让创建的线程复用。
- 在线程池当中总有几个活跃的线程,在需要使用线程的时候拿到一个空闲的线程,当工作完成了的时候,并不急这把这个线程关掉,而是把线程归还到线程池当中,方便给他人使用
- 总而言之,在使用线程池之后,开启线程就变成了在线程池当中找到一个空闲的线程,销毁线程变成了归还线程到线程池的过程
特点
- 控制最大线程并行数量
- 线程复用
- 管理线程
工作原理
举个例子:假如CorePool等于10,maxnumPoolSize=50,阻塞队列用ArrayBlockingQueue(50)
- 向线程池提交一个线程,先判断(工作的线程是否超过CorePool这里的corePool是10)
- 如果没有超过,会通过ThreadFactory创建一个新的xianc(被worker包装),然后持续任务
- 如果超过了,则会加入到阻塞队列里面去进行排队(这里阻塞队列用的的ArrayBlockingQueue长度为50)
- 如果队列满了(也就是超过了50),则会去判断当前的工作线程是否小于maxnumPoolSize(这里maxnumPoolSize是为50)如果小于则会,继续创建线程(包装成worker对象)
- 如果maxnumPoolSize也超过了的话就会执行拒绝策略。
ThreadPoolExecutor 构造函数
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTime
TimeUnit unit, // keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue, // 待执行的任务队列
ThreadFactory threadFactory, // 用于创建新线程的工厂类
RejectedExecutionHandler handler) { // 由于达到线程边界和队列容量而无法执行时使用的拒绝策略实现类
}
- corePoolSize(必需):核心线程数。即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut(必需)参数设置为true后,核心线程处于空闲一段时间以上,也会被回收。
- maximumPoolSize(必需):最大线程数量。当核心线程满了并且阻塞队列也满了之后,线程池会临时追加创建线程,直到线程数达到maximumPoolSize值的上限
- keepAliveTime(必需):当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTime,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
- unit(必需):时间单位
- workQueue(必需):任务队列,采用阻塞队列实现
- threadFactory(可选):线程工厂。指定线程池创建线程的方式。
- handler(可选):拒绝策略。当线程池中线程数达到maximumPoolSize且workQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。
阻塞任务队列
使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue;
- SynchronousQueue:是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
- LinkedBlockingQueue:无界队列(严格来说并非无界,上限是Integer.MAX_VALUE),基于链表结构。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。
- ArrayBlockingQueue:有界队列,基于数组实现。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。
另外,Java还提供了另外4种队列:
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayQueue:延迟队列。基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。
- LinkedBlockingDeque:双端队列。基于链表实现,既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。这个队列比较特别的时,采用一种预占模式,意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素。
拒绝策略
- AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
- CallerRunsPolicy:
- DiscardPolicy:直接丢弃任务,不抛出任何异常。
- DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。
线程池状态
Executors封装线程池
- FixedThreadPool: 创建固定容量线程池。特点是核心线程数等于最大线程数,意味着线程池只能创建核心线程,keepAliveTime为0,即线程执行完任务立即回收。任务队列未指定容量,代表使用默认值Integer.MAX_VALUE。适用于需要控制并发线程的场景。
- SingleThreadExecutor: 单线程线程池。特点是线程池中只有一个线程(核心线程),线程执行完任务立即回收,使用有界阻塞队列(容量未指定,使用默认值Integer.MAX_VALUE)
- ScheduledThreadPool: 定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时或周期性的任务。
- CachedThreadPool: 缓存线程池。没有核心线程,普通线程数量为Integer.MAX_VALUE(可以理解为无限),线程闲置60s后回收,任务队列使用SynchronousQueue这种无容量的同步队列。适用于任务量大但耗时低的场景。
源码解读
execute
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
/** ctl记录着workCount和runState */
int c = ctl.get();
/** case1: 如果线程池中的线程数量小于核心线程数,那么创建线程并执行*/
if (workerCountOf(c) < corePoolSize) { // workerCountOf(c): 获取当前活动的线程数
/**
* 在线程池中新建一个新的线程
* command:需要执行的Runnable线程
* true:新增线程时,【当前活动的线程数】是否 < corePoolSize
* false:新增线程时,【当前活动的线程数】是否 < maximumPoolSize
*/
if (addWorker(command, true)) {
// 添加新线程成功,则直接返回。
return;
}
// 添加新线程失败,则重新获取【当前活动的线程数】
c = ctl.get();
}
/** 第二步:如果当前线程池是运行状态 并且 任务添加到队列成功
* (即:case2: 如果workCount >= corePoolSize,创建线程往workQueue添加线程任务,等待执行)*/
// BlockingQueue<Runnable> workQueue 和 Runnable command
if (isRunning(c) && workQueue.offer(command)) { // 添加command到workQueue队列中。
// 重新获取ctl
int recheck = ctl.get();
// 再次check一下,当前线程池是否是运行状态,如果不是运行时状态,则把刚刚添加到workQueue中的command移除掉,并调用拒绝策略
if (!isRunning(recheck) && remove(command)) {
reject(command);
} else if (workerCountOf(recheck) == 0) { // 如果【当前活动的线程数】为0,则执行addWork方法
/**
* null:只创建线程,但不去启动
* false:添加线程时,根据maximumPoolSize来判断
*
* 如果 (workerCountOf(recheck) > 0, 则直接返回,在队列中的command稍后会出队列并且执行
*/
addWorker(null, false);
}
}
/**
* 第三步:满足以下两种条件之一,进入第三步判断语句
* case1: 线程池不是正在运行状态,即:isRunning(c)==false
* case2: workCount >= corePoolSize 并且 添加workQueue队列失败。即:workQueue.offer(command)==false
*
* 由于第二个参数传的是false,所以如果workCount < maximumPoolSize,则创建执行线程;否则,进入方法体执行reject(command)
*/
else if (!addWorker(command, false)) {
// 执行线程创建失败的拒绝策略
reject(command);
}
}
基本过程:
- 根据ctl来获取workcount和runState
- 如果ctl小于核心线程数那么会调用addWorke增加一个工作线程并添加任务
- 如果ctl大于核心线程数那么会判断当前线程池是运行状态 并且 任务添加到队列成功
- 放入成功后再次判断工作线程数是否是0,如果是则执行addWork方法,否则直接返回
- 最后一种情况是当线程池不是运行状态且任务添加到队列失败,则执行拒绝策略
addWorker(Runnable firstTask, boolean core) 方法
参数说明:
- Runnable firstTask:新创建的线程应该首先运行的任务(如果没有,则为空)。
- boolean core:该参数决定了线程池容量的约束条件,即当前线程数量以何值为极限值。参数为 true 则使用corePollSize 作为约束值,否则使用maximumPoolSize。
private boolean addWorker(Runnable firstTask, boolean core) {
// 外层循环:判断线程池状态
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/**
* 1.线程池为非Running状态(Running状态则既可以新增核心线程也可以接受任务)
* 2.线程为shutdown状态且firstTask为空且队列不为空
* 3.满足条件1且条件2不满足,则返回false
* 4.条件2解读:线程池为shutdown状态时且任务队列不为空时,可以新增空任务的线程来处理队列中的任务
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 内层循环:线程池添加核心线程并返回是否添加成功的结果
for (;;) {
int wc = workerCountOf(c);
// 校验线程池已有线程数量是否超限:
// 1.线程池最大上限CAPACITY
// 2.corePoolSize或maximumPoolSize(取决于入参core)
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 通过CAS操作使工作线程数+1,跳出外层循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 线程+1失败,重读ctl
c = ctl.get(); // Re-read ctl
// 如果此时线程池状态不再是running,则重新进行外层循环
if (runStateOf(c) != rs)
continue retry;
// 其他 CAS 失败是因为工作线程数量改变了,继续内层循环尝试CAS对线程数+1
// else CAS failed due to workerCount change; retry inner loop
}
}
/**
* 核心线程数量+1成功的后续操作:添加到工作线程集合,并启动工作线程
*/
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 下面代码需要加锁:线程池主锁
mainLock.lock();
try {
// 持锁期间重新检查,线程工厂创建线程失败或获取锁之前关闭的情况发生时,退出
int c = ctl.get();
int rs = runStateOf(c);
// 再次检验线程池是否是running状态或线程池shutdown但线程任务为空
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 线程已经启动,则抛出非法线程状态异常
// 为什么会存在这种状态呢?未解决
if (t.isAlive()) // precheck that t is startable
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 {
//线程启动失败,则进入addWorkerFailed
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
解读
1.外层循环判断线程池的状态是否可以新增工作线程。这层校验基于下面两个原则:
- 线程池为Running状态时,既可以接受新任务也可以处理任务
- 线程池为Stop状态时只能新增空任务的工作线程(worker)处理任务队列(workQueue)中的任务不能接受新任务
2.内层循环向线程池添加工作线程并返回是否添加成功的结果。
- 之后校验线程数是否已经超限制,是则返回false,否则进入下一步
- 通过CAS使工作线程数+1,成功则进入步骤3,失败则再次校验线程池是否是运行状态,是则继续内层循环,不是则返回外层循环
3.核心线程数量+1成功的后续操作:添加到工作线程集合,并启动工作线程
- 首先获取锁之后,再次校验线程池状态(具体校验规则见代码注解),通过则进入下一步,未通过则添加线程失
- 线程池状态校验通过后,再检查线程是否已经启动,是则抛出异常,否则尝试将线程加入线程池
- 检查线程是否启动成功,成功则返回true,失败则进入 addWorkerFailed 方法
addWorkerFailed
/**
* 回滚创建的工作线程
* 1> 从workers队列中移除worker(如果存在队列中的话)。
* 2> workerCount减一。
* 3> 重新检查是否终止(termination),以防止这个worker的存在阻止了终止(termination)
*/
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null) {
workers.remove(w); // 步骤1:从work队列中移除w
}
// 步骤2:workerCount减1
decrementWorkerCount();
// 步骤3:
tryTerminate();
} finally {
mainLock.unlock();
}
}
/**
* 如果(state是SHUTDOWN状态,并且线程池和队列都为空)或者(state是STOP状态,并且线程池和队列都为空),则将state状态转换为TERMINATED。
* 如果可以终止,但workerCount非零,则中断空闲的worker,以确保关闭信号传播。
* 必须在可能终止操作的任何操作之后调用此方法-减少worker计数或在关闭过程中从队列中删除任务。
* 该方法是非私有的,以允许从ScheduledThreadPoolExecutor访问。
*/
final void tryTerminate() {
for (; ; ) {
int c = ctl.get();
/**
* 只有runState=STOP 或 SHUTDOWN(且workQueue为空)才能继续往下执行
*/
if (isRunning(c) || // 判断c是否是RUNNING状态
runStateAtLeast(c, TIDYING) || // 判断c是否是TIDYING或者TERMINATED
(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) { // 判断如果c是否是SHUTDOWN状态并且workQueue不为空
return;
}
// 当前活动的线程数不为0
if (workerCountOf(c) != 0) { // Eligible(有资格的) to terminate
// 因为ONLY_ONE=true,所以只针对一个闲置线程执行interrupt()
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // 尝试将当前线程池的运行状态设置为TIDYING,活跃线程数量设置为0
try {
terminated(); // 空方法
} finally {
ctl.set(ctlOf(TERMINATED, 0)); // 将当前线程池的运行状态设置为TERMINATED,活跃线程数量设置为0
termination.signalAll(); // 唤醒所有线程
}
return;
}
} finally {
mainLock.unlock();
}
}
}
流程
- 把worker吧workers移除
- workerCount -1
- 重新检查是否终止(termination),以防止这个worker的存在阻止了终止(termination)。
runWorker
1、判断当前任务或者从任务队列中获取的任务是否不为空,都为空则进入步骤2,否则进入步骤3
2、任务为空,则将completedAbruptly置为false(即线程不是突然终止),并执行processWorkerExit(w,completedAbruptly)方法进入线程退出程序
3、任务不为空,则进入循环,并加锁
4、判断是否为线程添加中断标识,以下两个条件满足其一则添加中断标识:
- 线程池状态>=STOP,即STOP或TERMINATED
- 一开始判断线程池状态<STOP,接下来检查发现Thread.interrupted()为true,即线程已经被中断,再次检查线程池状态是否>=STOP(以消除该瞬间shutdown方法生效,使线程池处于STOP或TERMINATED)
5、执行前置方法 beforeExecute(wt, task)(该方法为空方法,由子类实现)后执行task.run() 方法执行任务(执行不成功抛出相应异常)
6、执行后置方法 afterExecute(task, thrown)(该方法为空方法,由子类实现)后将线程池已完成的任务数+1,并释放锁。
7、再次进行循环条件判断。
processWorkerExit()
processWorkerExit(Worker w, boolean completedAbruptly)执行线程退出的方法
参数说明:
- Worker w:要结束的工作线程。
- boolean completedAbruptly: 是否突然完成(异常导致),如果工作线程因为用户异常死亡,则completedAbruptly参数为 true
/**
* Performs cleanup and bookkeeping for a dying worker. Called
* only from worker threads. Unless completedAbruptly is set,
* assumes that workerCount has already been adjusted to account
* for exit. This method removes thread from worker set, and
* possibly terminates the pool or replaces the worker if either
* it exited due to user task exception or if fewer than
* corePoolSize workers are running or queue is non-empty but
* there are no workers.
*
* @param w the worker
* @param completedAbruptly if the worker died due to user exception
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
/**
* 1.工作线程-1操作
* 1)如果completedAbruptly 为true,说明工作线程发生异常,那么将正在工作的线程数量-1
* 2)如果completedAbruptly 为false,说明工作线程无任务可以执行,由getTask()执行worker-1操作
*/
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
// 2.从线程set集合中移除工作线程,该过程需要加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 将该worker已完成的任务数追加到线程池已完成的任务数
completedTaskCount += w.completedTasks;
// HashSet<Worker>中移除该worker
workers.remove(w);
} finally {
mainLock.unlock();
}
// 3.根据线程池状态进行判断是否结束线程池
tryTerminate();
/**
* 4.是否需要增加工作线程
* 线程池状态是running 或 shutdown
* 如果当前线程是突然终止的,addWorker()
* 如果当前线程不是突然终止的,但当前线程数量 < 要维护的线程数量,addWorker()
* 故如果调用线程池shutdown(),直到workQueue为空前,线程池都会维持corePoolSize个线程,然后再逐渐销毁这corePoolSize个线程
*/
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);
}
}
流程
1、如果 completedAbruptly 为 true,即工作线程因为异常突然死亡,则执行工作线程-1操作。
2、主线程获取锁后,线程池已经完成的任务数追加 w(当前工作线程) 完成的任务数,并从worker的set集合中移除当前worker。
3、根据线程池状态进行判断是否执行tryTerminate()结束线程池。
4、是否需要增加工作线程,如果线程池还没有完全终止,仍需要保持一定数量的线程。
- 如果当前线程是突然终止的,调用addWorker()创建工作线程
- 当前线程不是突然终止,但当前工作线程数量小于线程池需要维护的线程数量,则创建工作线程。需要维护的线程数量为corePoolSize(取决于成员变量 allowCoreThreadTimeOut是否为 false)或1。
https://blog.csdn.net/qq_32828253/article/details/112297856?spm=1001.2014.3001.5502
https://blog.csdn.net/mu_wind/article/details/113806680