聊聊线程池,ThreadPoolExecutor源码详解

JAVA烂猪皮

共 20017字,需浏览 41分钟

 ·

2021-06-10 03:21

走过路过不要错过

点击蓝字关注我们


    

一、线程池起步

1. 线程池的基本介绍

首先Java里的线程利用的线程模型是KLT,这带来了许多好处,比如线程的阻塞不会带来进程的阻塞,能更加高效地利用CPU的资源等。但这也意味着在Java里的线程的创建和销毁是一个相对偏且消耗资源的操作,Java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为避免资源过度消耗需要设法重用线程执行多个任务。


线程池就起到这么一个重用,它负责了线程缓存,也负责对线程进行统一的分配、调优与监控。通过多个任务重用线程,实现线程复用的效果,最终减少系统开销;

1.1 什么时候使用线程池?

  • 单个任务处理时间比较短;

  • 需要处理的任务数量很大;

1.2 线程池的优势

  • 重用存在的线程,减少线程创建、消亡的开销,提高性能;

  • 提高响应速度,当任务到达时,任务可以不需要等待线程创建就能立即执行;

  • 提高线程的可管理性,可统一分配,调优和监控;

2. Java的线程池相关类

二、Executor框架接口

Executor框架是一个用户级的调度器,用以执行策略调用,执行和控制,目的是提供一种将“任务执行”与“任务运行”分离开来的机制;

Executor框架中最为相关的接口有以下三个:

  • Executor:接口类,它是Executor框架的基础,将任务的提交与任务的执行分离开来;

  • ExecutorService:扩展了 Executor 接口,添加了一些用来管理执行器生命周期和任务的声明周期的相关方法;

  • ScheduledExecutorService:扩展了ExecutorService接口,支持在给定的延迟后运行命令,或者定期执行命令;

1. Executor接口

public interface Executor {

/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*/

void execute(Runnable command);
}

Executor接口中只有一个函数,用来替代我们原先启动线程的方式,我们可以简单看下对比:

// ===:原先线程启动方式
Thread t = new Thread();
t.start();

// ===:将任务交由Executor去处理
Thread t = new Thread();
executor.executor(t);

最终Executor会将任务交由具体的实现类处理,不同的实现类处理在不同情况下处理的方式不同,可能是立即创建一个新线程并启动,也有可能是使用已有的工作线程来运行传入的任务,也可能是根据设置线程池的容量或者阻塞队列的容量来决定是否要将传入的线程放入阻塞队列中或者拒绝接收传入的线程。

2. ExecutorService接口

ExecutorService扩展了Executor接口,是一个比 Executor 使用更广泛的子类接口,其提供了生命周期管理的方法;调用ExecutorService的shutdown()方法可以平滑地关闭 ExecutorService,调用该方法后,将导致 ExecutorService 停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭 ExecutorService。如果需要支持即时关闭,也就是shutDownNow()方法,此时我们需要考虑任务中断的处理。

3. ScheduledExecutorService接口

ScheduledExecutorService 扩展 ExecutorService 接口并增加了 schedule() 、scheduleAtFixedRate()等方法。调用 schedule() 方法可以在指定的延时后执行一个 Runnable 或者Callable 任务。调用 scheduleAtFixedRate()方法和scheduleWithFixedDelay()方法可以按照指定时间间隔定期执行任务。

三、ThreadPoolExecutor解析

ThreadPoolExecutor类实现 Executor 框架中最为核心的一个类,它是线程池的实现类,接下来介绍下它的主要实现代码:

1. ThreadPoolExecutor的参数

首先来看类中定义的几个关键字段,如下:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 32 - 3
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

这里的ctl是用来保存线程池运行状态(runState)和线程池内有效线程数量(workerCount)的一个字段,声明为一个 AtomicInteger 对象,主要包括了两部分信息:高3位保存运行状态,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位再 - 1,这个常量表示 workerCount 的最大值,即2的29次方。

接下来定义的几个字段用来表示线程池的状态,一个有五种状态,这里做一个简单的说明:

  • RUNNING:能接受新提交的任务,以及对已添加的任务进行处理;

  • SHUTDOWN:处于shutdown状态下的线程池不能接收新的任务,但能处理已经提交的任务;

  • STOP:线程池处于此状态下,不接收新的任务,也不会处理已经提交的任务,会将已经提交的任务中断;

  • TIDYING:当所有的任务已终止,ctl记录的有效线程数量为0时,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated(),该函数在 ThreadPoolExecutor 类中是空的,若用户想在线程池变为 TIDYING 时,进行相应的处理,可以通过重载该函数来实现;

  • TERMINATEDterminated() 方法被执行完后进入该状态,线程池彻底终止,变成TERMINATED状态。

2. execute函数

首先我们先来了解ThreadPoolExecutor执行execute()方法的示意图:

通过上图我们能了解,线程池有三个梯度,分别是核心线程池、等待队列、整个线程池,这三个梯度在代码的执行流程中发挥了重要的作用,接下来我们来了解它的源码执行过程,如下所示:

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();

// 获取clt,即获取runState和workerCount
int c = ctl.get();

// workerCountOf(c)取出低29位的值即当前活动的线程数,如果小于corePoolSize,
// 则新建一工作线程放入线程池中,并将该线程用来执行当前任务
if (workerCountOf(c) < corePoolSize) {

// addWorker中的第二个参数表示限制添加线程的数量是根据 corePoolSize 来判断还是 maximumPoolSize 来判断;
// 如果为true,根据corePoolSize来判断;如果为false,则根据maximumPoolSize来判断
if (addWorker(command, true))
// 添加成功直接返回
return;
// 如果添加失败,则重新获取ctl值
c = ctl.get();
}

// 如果当前线程池是运行状态并且任务添加到队列成功
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取ctl值
int recheck = ctl.get();
// 再次判断线程池的运行状态,若不是运行状态,由于之前已经把command添加到队列中了,现在需要移除该command
if (!isRunning(recheck) && remove(command))
// 使用拒绝策略对该任务进行处理
reject(command);

// 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
// 如果判断workerCount大于0,则直接返回,因为任务已经加入到队列中了,之后会被调度执行,这里不操心、。
else if (workerCountOf(recheck) == 0)
// 添加一个新的工作线程,任务已经在工作队列里了,所以第一个参数为null
addWorker(null, false);
}

/*
* 代码如果走到这,那么有以下两种情况:
* 1. 线程池已经不是RUNNING状态了;
* 2. 线程池是RUNNING状态,但现有线程>=核心线程池的数量,并且Blocking队列已满,
* 那么线程到第三个梯度,整个线程池了,这时,再次调用addWorker方法,
* 第二个参数传入为false,将线程池的有限线程数量设置为maximumPoolSize;
*/

else if (!addWorker(command, false))
// 如果失败则拒绝该任务
reject(command);
}

简单来说,如果线程池一直处于 RUNNING 状态的话,则函数的执行流程如下:

  1. 如果当前运行的线程少于 corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁,后面再介绍);

  2. 如果运行的线程等于或多于 corePoolSize,则将任务假如到 BlockingQueue;

  3. 如果无法将任务加入 BlockingQueue(队列已满),在创建新的线程来处理任务(注意,执行这一步需要获取全局锁);

  4. 如果创建新线程将使用当前运行的线程大于 maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.reijectedExecution()方法;

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能避免记获取全局锁,那将会是一个很严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于大于corePoolSize),几乎所有的 execute()方法调用都是通过执行步骤2,而步骤2不需要获取全局锁。

3. 构造函数

接下来我们来了解如何创建一个新的线程池,它的构造函数的源代码如下:

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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

简单介绍下构造函数中几个变量的定义:

  • corePoolSize:定义核心线程的数量,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲线程的基本线程能够执行新任务;

  • maximumPoolSize:定义线程池最大线程的数量;

  • keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime

  • unit:时间单位,为 keepAliveTime 指定时间单位;

  • workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列,它有以下几个实现类可以选择:

    • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序;

    • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量要高于 ArrayBlockingQueue;

    • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态;

    • PriorityBlockingQueue:一个具有优先级的无限阻塞队列;

  • threadFactory:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的 NORM_PRIORITY 优先级并且是非守护线程,同时也设置了线程的名称。

  • handler:它是 RejectedExecutionHandler 类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:

    • AbortPolicy:直接抛出异常,这是默认策略;

    • CallerRunsPolicy:用调用者所在的线程来执行任务;

    • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

    • DiscardPolicy:直接丢弃任务;

4. addWorker函数

上面我们讲execute()函数时,提到了addWorker()函数,该函数的作用主要就是在线程池中新建一个新的线程并执行,firstTask参数表示指定新增的线程执行的第一个任务,core参数为 true 表示在此时线程数量的上限为corePoolSize,当这个参数为 false 时,表示此时代码判断的线程数量上限为maximunPoolSize,这一点在前面代码已经有所提及。源代码如下所示:

private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 获取运行状态
int rs = runStateOf(c);

// 检查队列是否在必要的时候为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;

// 这一循环内主要就是通过CAS增加线程个数
for (;;) {
int wc = workerCountOf(c);
// 如果线程数量超出限制,返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS增加workerCount,如果成功,则跳出第一个for循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果增加workerCount失败,则重新获取ctl的值
c = ctl.get();
// 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}

// 执行到这里说明CAS增加新线程个数成功了,下面开始要创建新的工作线程Worker

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 根据firstTask来创建 Worker对象
w = new Worker(firstTask);
// Worker对象内部维护着一个线程对象
final Thread t = w.thread;
if (t != null) {
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 重新检查线程池状态,以免在获取锁之前调用shutdown方法改变线程池状态
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN表示是 RUNNING 状态;
// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且 firstTask 为null,向线程池中添加线程。
// 因为在 SHUTDOWN 时不会在添加新的任务,但还是会执行workQueue中的任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
// workers是一个HashSet
workers.add(w);
int s = workers.size();
// largestPoolSize记录着线程池中出现过的最大线程数量
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
// 释放锁
mainLock.unlock();
}
if (workerAdded) {
// 启动线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

上面代码的最后几行,会判断添加工作线程是否成功,如果失败,会执行addWorkerFailed()函数,将任务从workers中移除,并且做workerCount-1操作,简单看一下该函数的源代码:

private void addWorkerFailed(Worker w) {

final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// 如果worker不为null
if (w != null)
// workers移除worker
workers.remove(w);
// 通过CAS操作使 workerCount-1
decrementWorkerCount();
tryTerminate();
} finally {
// 释放锁
mainLock.unlock();
}
}

5. Worker对象

回顾上面代码,我们发现线程池创建新的工作线程都是去创建一个新的 Worker 对象,事实上线程池中的每一个工作线程都被封装为Worker对象,ThreadPool 其实就是在维护着一组 Worker 对象,接下来我们来了解Worker类的源代码,如下:

private final class Worker 
extends AbstractQueuedSynchronizer
implements Runnable
{

private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
// 给出初始firstTask,由线程创建工厂创建新的线程
Worker(Runnable firstTask) {
// 将状态设置为-1,防止被调用前就被中断
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
// 将run方法的运行委托给外部runWorker()函数
public void run() {
runWorker(this);
}

// 关于同步状态(锁)
//
// 同步状态state=0表示锁未被获取
// 同步状态state=1表示锁被获取
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}

Worker类继承了AQS,并实现了Runnable接口,其中 firstTask 属性用来保存传入的任务,而 thread 属性是在调用构造方法时传入的 ThreadFactory 创建出来的线程,是用来处理任务的线程。

在调用构造方法时,需要把任务传入,这里通过getThreadFactory().newThread(this);来新建一个线程,newThread() 方法传入的参数是 this;因为 Worker 本身继承了 Runnable 接口,也就是一个线程,所以一个 Worker 对象在启动的时候会调用 Worker 类中的 run 方法。而run方法中又调用了runWorker()方法。

6. runWorker函数

通过上面源码的了解,我们知道任务最终的执行是在runWorker()函数中,接下来我们就通过源码来了解这一过程:

final void runWorker(Worker w) {

Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 调用unlock(),将state设置为0,允许中断
w.unlock();
// 是否因为异常退出循环
boolean completedAbruptly = true;
try {
// 如果task为空,则通过getTask()从队列中获取任务
while (task != null || (task = getTask()) != null) {
// 加锁
w.lock();
// 如果线程池已被停止(STOP)(至少大于STOP状态),要确保线程都被中断
// 如果状态不对,检查当前线程是否中断并清除中断状态,并且再次检查线程池状态是否大于STOP
// 如果上述满足,检查该对象是否处于中断状态,不清除中断标记
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
// 中断该对象
wt.interrupt();
try {
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 {
afterExecute(task, thrown);
}
} finally {
task = null;
// 更新当前已经完成任务数量
w.completedTasks++;
// 释放锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 执行清理工作,处理并退出当前 worker
processWorkerExit(w, completedAbruptly);
}
}

看完上面代码,我们可以大致地了解runWorker()函数的执行流程:

  1. 首先执行unlock()方法,将Worker的state置为0这样工作线程就可以被中断了(因为后续线程池关闭操作需要去中断线程);

  2. 判断当前工作的任务(当前工作线程中的task,或者从任务队列中取出的task)是否为null,如果不为null就往下执行,为null就执行processWorkerExit()方法;

  3. 获取工作线程内部持有的独占锁(避免在执行任务期间,其他线程调用shutdown后正在执行的任务被中断,shutdown只会中断当前被阻塞挂起的没有执行任务的线程);

  4. 之后执行beforeExecute()函数,该方法为扩展接口代码,表示在具体执行任务之前出一些处理,然后就开始执行task.run()函数去真正地执行具体任务,执行完之后会调用afterExecute()方法,用以处理任务执行完毕之后的工作,也是一个扩展接口代码;

  5. 更新当前线程池完成的任务数,并释放锁;

7. getTask函数

上面runWorker()函数中我们稍微提及了getTask()函数,该函数表示从阻塞队列中获取任务,接下来我们就来进行了解,其源代码如下:

private Runnable getTask() {
// timeOut变量的值表示上次从阻塞队列中取任务时是否超时
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);

// 如果线程池处于非RUNNING状态或者处于STOP以上状态并且等待队列为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 将workCount-1并返回null
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);

// timed 变量用于判断是否需要进行超时控制。
// allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
// wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
// 对于超过核心线程数量的这些线程,需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

/*
* wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
* timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
* 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减1;
* 如果减1失败,则返回重试。
* 如果wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
*/

if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null;
* 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。
*/

Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 如果 r == null,说明已经超时,timedOut设置为true
timedOut = true;
} catch (InterruptedException retry) {
// 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
timedOut = false;
}
}
}

这里主要讲一下第二个 if 判断,这段代码的目的是为了控制线程池的有效线程数量:

由上文中的分析可以知道,在执行execute方法时,如果当前线程池的线程数量超过了corePoolSize 且小于 maximumPoolSize,并且 workQueue 已满时,则可以增加工作线程,但这时如果超时没有获取到任务,也就是 timedOut 为true的情况,说明 workQueue 已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的线程销毁掉,保持线程数量在corePoolSize即可。

什么时候会销毁?当然是runWorker()方法执行完之后,也就是Worker中的run方法执行完,由 JVM 自动回收。getTask()函数返回null时,在runWorker()方法中会跳出while循环,最后会执行processWorkerExit()方法。

8. processWorkerExit函数

private void processWorkerExit(Worker w, boolean completedAbruptly) {

// 如果completedAbruptly值为true,则说明线程执行时出现了异常,需要将workerCount减1;
if (completedAbruptly)
decrementWorkerCount();

// 获取全局锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 统计完成的任务数
completedTaskCount += w.completedTasks;
// 从 workers 中移除一个工作线程
workers.remove(w);
} finally {
// 释放锁
mainLock.unlock();
}

// 根据线程池状态判断是否结束线程池
tryTerminate();

int c = ctl.get();

// 判断当前线程池中的数量是否少于核心线程数
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
// allowCoreThreadTimeOut表示是否允许核心线程超时,默认为false
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果min等于0并且队列不为空
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 现有工作线程大于min,直接返回
if (workerCountOf(c) >= min)
return;
}
addWorker(null, false);
}
}

我们大致来缕清下processWorkerExit()函数的工作流程:

  1. 首先获取全局锁,之后对线程池完成的任务个数进行统计,之后再从工作线程的集合中移除当前工作线程,完成清理工作;

  2. 调用tryTerminate()函数,根据线程池状态判断是否结束线程池,下面详细讲该函数实现;

  3. 判断当前线程池中的线程个数是否小于核心线程数,如果是就新增一个线程保证有足够的线程可以执行任务队列中的任务或者提交的新任务;

9. tryTerminate方法

tryTerminate()方法的主要工作就是根据线程池状态进行判断是否结束线程池,代码如下:

final void tryTerminate() {
for (;;) {
int c = ctl.get();
/*
* 当线程池的状态为以下几种情况时,直接返回,不调用terminated():
* RUNNING:线程池还在运行中,不能停止去terminated;
* TIDYING或TERMINATED:因为线程池中已经没有正在运行的线程了;
* SHUTDOWN并且等待队列非空:需要先执行完workQueue中的task;
*/

if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;

// 如果线程数量不为0,则中断一个空闲的工作线程,并返回
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}

// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 通过CAS尝试设置状态为TIDYING,如果设置成功,则调用terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 调用terminated()方法
terminated();
} finally {
// 设置状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
// 解锁
mainLock.unlock();
}
// else retry on failed CAS
}
}

这里需要注意的一点是,terminated()默认什么都不实现,需要继承类根据业务场景去完成该方法;




往期精彩推荐



腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)

面试:史上最全多线程面试题 !

最新阿里内推Java后端面试题

JVM难学?那是因为你没认真看完这篇文章


END


关注作者微信公众号 —《JAVA烂猪皮》


了解更多java后端架构知识以及最新面试宝典


你点的每个好看,我都认真当成了


看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力


作者:周二鸭

出处:https://www.cnblogs.com/jojop/p/14118479.html

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报