文章目录
- 1. 为什么要使用线程池
- 2. 线程池是如何做的
- 3. 核心源码解析
- 1. 关键字段解释
- 1. ctl
- 2. runState 线程池的状态
- 2. 线程数控制字段
- 2. Worker内部类
- 1. 构造方法
- 2. 一般方法
- 1. run()
- 2. isHeldExclusively()
- 3. tryAcquire(int unused)
- 4. tryRelease(int unused)
- 4. Worker源码及注解
- 3. 构造方法
- 4. 核心方法讲解
- 1. execute()
- 1.1 addWorker(Runnable firstTask, boolean core)
- 1.1.1 addWorkerFailed()
- 1.1.2 decrementWorkerCount()
- 1.1.3 tryTerminate()
- 2. shutdown()
- 2.1 advanceRunState(int targetState)
- 2.2 interruptIdleWorkers()
- 2.3 tryTerminate()
- 3. shutdownNow()
- 3.1 interruptWorkers()
- 3.2 drainQueue()
- 2. awaitTermination()
- 4.总结
- 5. 参考文章
前言:本节作为JUC框架的第五节,主要介绍线程池核心类
ThreadPoolExecutor
的作用,在前一章节中讲解了
ThreadPoolExecutor
类的继承关系
【JUC-04】JUC—与线程池有关的Executor框架介绍本节主要来分析线程池的核心实现类,在阅读本文之前最好先了解一下AQS中的作用,因为线程池核心的实现同样是继承于
AQS
1. 为什么要使用线程池
线程的生成和销毁都是需要消耗系统的资源,因此可以提前准备好一堆可以使用的线程、供任务调度的使用。主要有以下几点好处:
- 降低资源的消耗。通过重复利用已经创建的线程降低线程创建和线程销毁的资源损耗。
- 提高响应速度。当任务达到时,不需要等待线程创建就可以执行
- 提供线程的可管理性。线程池本身提供了很多方法供用户监控、设置线程池。
2. 线程池是如何做的
从宏观而言,线程池主要的处理思路如下图:
该图引自java并发编程艺术
在分析线程池处理过程之前,需要明确明个不同的概念,一个是使用者、一个线程工作者,使用者是面向于使用线程池的开发人员,而线程工作者是线程池内部的一个工作线程,开发人员提交一个任务,由内部的工作线程处理。
线程池接受一个新提交的任务时解决思路如下:
- 判断线程池工作中线程是否已经超过核心线程数。如果没有超过,则创建一个工作线程来执行任务;如果正在工作的线程数超过了核心线程数,则进入下一个流程。
- 判断线程池的阻塞队列(等待队列)是否已经被占满。如果没有占满,则将任务添加到阻塞队列中;如果已经占满,则进入下一阶段。
- 判断线程池工作中线程是否已经超过最大线程数。如果没有超过,则创建一个新的工作线程来执行任务;如果超过了最大线程数,则需要根据设置的拒绝策略来处理新的任务。
3. 核心源码解析
1. 关键字段解释
1. ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
原子性整数的ctl主要为了表示两个含义:
- workerCount:表示有效的线程数(指的是允许start但不允许stop的工作线程),当然该值也是一个估计值(可能动态变化),并且框架暂时定了该值的最大值。(低位)
- runState:表示线程池状态.(高三位)
/**
* count_bits位数为29
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* workerCount的最大大小为2^29-1(位运算)
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
2. runState 线程池的状态
主要有以下几种状态,各个状态含义如下,
- RUNNING: 接收新任务及处理等待队列中的任务
- SHUTDOWN: 不接受新任务但是处理等待队列中的任务
- STOP: 不接受新任务、不处理等待队列中任务、中断正在处理的线程
- TIDYING: 所有的任务已经终止,并且workerCount = 0,线程转化为TIDYING时,将会调用terminated()方法
- TERMINATED: terminated()方法已经执行完毕,代表线程已经成功关闭。
针对每个状态的流状态如下图:
各个状态数据,每个状态都存储在二进制的高位,具体数值如下
/**
* 将高位1左移29位再取反
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* SHUTDOWN 0 * 2^29
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* STOP 将1左移29位 等效于 1 * 2^29
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* TIDYING 2 * 2^29 等效于将10左移29位
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* TERMINATED 3 * 2^29 等效于将11左移29位
*/
private static final int TERMINATED = 3 << COUNT_BITS;
其中计算各个状态分别用二进制计算来表示:
/**
* 主要是计算runState、workerCount、及ctl等三个元素
* @param c
* @return
*/
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
2. 线程数控制字段
- corePoolSize:核心线程数,当工作线程数小于该值时,新来的任务会创建新线程来处理,并且线程会一直存活,不会过期。
private volatile int corePoolSize;
- maximumPoolSize:线程池最大线程数。当工作中的线程数已经超过了最大线程数时,会默认启用拒绝策略
private volatile int maximumPoolSize;
- keepAliveTime:线程空闲之后过期的时间,只有线程数大于corePoolSize时或者开启allowCoreThreadTimeOut参数时,该值才起作用
private volatile long keepAliveTime;
- allowCoreThreadTimeOut:是否允许核心线程过期
默认是否false,此时,核心线程空闲也不会过期;如果是true,核心线程会等keepAliveTime时间,然后自动过期
private volatile boolean allowCoreThreadTimeOut;
其他还有一些变量,在后续的讲解中再加以介绍,防止大家突然接收太多参数,比较困惑。
2. Worker内部类
Worker
内部类可以理解为任务的处理线程,该类本身继承于AQS类,内部包含了很多AQS的特性。因此该内部类肯定有很多获取锁、释放锁等方法。
该类组成的worker对象本身是放在hashSet中,这里可能有人会问,为什么在一个做多线程事儿的类中却放的是线程不安全的容器呢?
private final HashSet<Worker> workers = new HashSet<Worker>();
因为该框架设计者提供了一个mainLock
锁的工具,线程在访问hashset
集合之前必须首先获取锁,设计者**(也就是Doug Lea)**也是综合考虑线程安全集合和锁的效率,综合对比,选择利用锁保证线程安全。
/**
* 持有mainLock锁的才允许访问工作线程集合
* 为什么不用线程安全的集合呢?
* 经过测试发现通常情况下,利用lock比并发集合性能更佳。
*/
private final ReentrantLock mainLock = new ReentrantLock();
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
/**
* 用于存储线程池中所有的工作线程、只有得到mainLock锁时才允许访问
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
1. 构造方法
在调用构造方法时,需要把任务传入。通过getThreadFactory().newThread(this)
方法来新建一个线程,newThread
方法传入的参数是this,
因为Worker
本身继承了Runnable
接口,也是一个线程。所以一个Worker对象在启动的时候会调用Worker类中的run方法。
Worker(Runnable firstTask) {
// 初始化状态为RUNNING
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
2. 一般方法
1. run()
该方法是Worker线程类的核心方法,本身是调用runWorker()来启动一个线程,该方法主要提供给外部调用者使用。
public void run() {
runWorker(this);
}
2. isHeldExclusively()
实现AQS中的isHeldExclusively()
方法,判断是否被某个线程独占
/**
* 实现AQS中的isHeldExclusively()方法,判断是否被某个线程独占
* 0 表示没有锁定状态
* 1 表示锁定状态
* @return
*/
protected boolean isHeldExclusively() {
return getState() != 0;
}
3. tryAcquire(int unused)
获取同步状态,该方法实现AQS
中tryAcquire()
方法,该方法是可重入的。主要处理思路如下:
- 更新state状态,将该值设置为1
- 设置当前线程占有同步状态
- 如果获取成功返回true,获取失败,返回false
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
4. tryRelease(int unused)
释放同步状态,主要思路如下:
- 释放线程独占
- 设置线程状态为SHUTDOWN
这里为什么不用CAS更新state呢?因为只有拥有同步状态时才可以释放,不存在多线程问题
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
4. Worker源码及注解
/**
* Worker继承自AbstractQueuedSynchronizer并且实现了Runnable接口
* 利用AQS机制,在任务执行的时候控制锁的获取及释放。
* 这样可以使正在唤醒等待任务的工作线程被中断,而不是中断正在运行的任务。
* 使用的是简单的非重入互斥锁,而不是重入锁。不希望工作线程调用线程池控制变量方法时重复请求,不希望重复获取锁
* 为了防止中断,线程开始启动的时候设置为负值
*
* AQS中state字段表示线程是否拥有同步锁
* 1 表示占有
* 0 表示没有占有
*/
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/**
* 在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程。
*/
final Thread thread;
/**
* 初始化运行时的任务
* */
Runnable firstTask;
/** Per-thread task counter */
/**
* 每个线程的计时器,用于统计已经完成的任务
*/
volatile long completedTasks;
/**
* 在调用构造方法时,需要把任务传入。
* 通过getThreadFactory().newThread(this)方法来新建一个线程,newThread方法传入的参数是this,
* 因为Worker本身继承了Runnable接口,也是一个线程。
* 所以一个Worker对象在启动的时候会调用Worker类中的run方法。
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
// 初始化状态为RUNNING
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/**
* 将内部runWorker()包装成run()给外部使用
*/
public void run() {
runWorker(this);
}
// Lock methods
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
/**
* 实现AQS中的isHeldExclusively()方法,判断是否被某个线程独占
* 0 表示没有锁定状态
* 1 表示锁定状态
* @return
*/
protected boolean isHeldExclusively() {
return getState() != 0;
}
/**
* 获取同步状态
* 该方法实现AQS中tryAcquire()方法,
* 该方法不允许重入
* 1. 更新state状态,将该值设置为1。如果失败,则直接返回false
* 2. 设置当前线程占有同步状态
* 3. 如果获取成功返回true,获取失败,返回false
* @param unused
* @return
*/
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 释放同步状态
* 主要操作:
* 1. 释放线程独占
* 2. 设置线程状态为SHUTDOWN
* 这里为什么不用CAS更新state呢?因为只有拥有同步状态时才可以释放,不存在多线程问题
* @param unused
* @return
*/
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/**
* 下面三个方法直接给ThreadPoolExecutor使用
* 本身也是调用了AQS底层的acquire()及release()方法
*/
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) {
}
}
}
}
3. 构造方法
ThreadPoolExecutor
主要提供了四种构造方法,整体功能都差不多,本次只讲解参数最多的一个构造方法,其他的方法只不过是减少了几个参数,使用默认的参数配置。
/**
* 创建一个线程池,可以通过给定初始化的参数
* 系统推荐了更方便的{@link Executors}工具类
* @param corePoolSize 存在线程池中的线程数、如果没有设置allowCoreThreadTimeOut值,即使空闲线程也会保留
* @param maximumPoolSize 线程池中允许最大工作线程
* @param keepAliveTime 当实际线程数超过核心线程数时,空闲线程可以存活的最长时间
* @param unit keepAliveTime的时间单位
* @param workQueue 阻塞队列,线程数超过corePoolSize时,请求的线程会先进入阻塞队列中,只有当阻塞队列也满了,才会去创建线程
* @param threadFactory 创建线程的工厂,所有的线程都是通过该工厂创建
* @param handler 线程满时或者停止时的拒绝策略
*/
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;
}
主要讲解一下拒绝策略,其中JDK本身提供了四种线程满时的拒绝策略:
分别是
- CallerRunsPolicy
/**
* 拒绝策略:调用者线程来处理被拒绝的任务
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
- AbortPolicy
/**
* 拒绝策略:直接拒绝,并且往外抛出异常,该策略为JDK默认的拒绝策略
*/
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- DiscardPolicy
/**
* 拒绝策略:直接废弃被拒绝的任务
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
- DiscardOldestPolicy
/**
* 拒绝策略:废弃最早被提交未被处理的请求
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
4. 核心方法讲解
首先根据该类的继承关系一次介绍ThreadPoolExecutor
中的核心方法。
1. execute()
为什么第一个首先讲解该方法呢,该方法是处理一个任务的主要方法,该方法主要实现了Executor
接口中的execute()
方法,该方法其实就是本文头部第二节中的线程池是如何做的。
思路如下:
提交任务,主要分三种情况处理
- 创建新线程来处理
- 可能会放到已存在的线程池中
- 还可能不会提交任务,因为执行器可能中断、或者已经达到了最大的线程池容量,拒绝策略开始生效
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//clt记录着runState和workerCount
int c = ctl.get();
/**
* workerCountOf方法取出低29位的值,表示当前活动的线程数;
* 第一种情况:
* 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中;并且把任务放到线程池中
* 如果添加失败了,则重新获取ctl值
*/
if (workerCountOf(c) < corePoolSize) {
/**
* addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断;
* 如果为true,根据corePoolSize来判断;
* 如果为false,则根据maximumPoolSize来判断
*/
if (addWorker(command, true))
return;
c = ctl.get();
}
/**
* 第二种情况:如果当前线程池是运行状态并且任务可以添加到队列
* 重新获取ctl值,判断状态,因为有可能在上次检查之后线程再次改变状态
*/
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
/**
* 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,这时需要移除该command
* 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
*/
if (! isRunning(recheck) && remove(command))
reject(command);
/**
* 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
* 这里传入的参数表示:
* 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
* 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
* 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/**
* 第三种情况:需要执行拒绝策略
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是RUNNING状态;
* 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
* 再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
* 如果失败则拒绝该任务
*/
else if (!addWorker(command, false))
reject(command);
}
1.1 addWorker(Runnable firstTask, boolean core)
判断是否开启一个新的线程来处理任务。
firstTask:表示想要添加的任务
core:表示是根据corePoolSize、还是maximumPoolSize判断线程添加的门槛
/**
* 判断在当前的线程池状态及corePoolSize、maxPoolSize值下,是否新添加一个新的任务
* 如果添加成功,线程池参数跟随调整,
* 如果添加失败,表示线程池状态为stop、准备shutdown,或者线程工厂创建线程失败。
* @param firstTask 第一个开始启动的任务
* @param core 如果是依据corePoolSize判断则为true,maximumPoolSize判断为false
* @return 如果插入成功,则返回true,否则返回false
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
/**
* 在下面两层循环中,先循环的获取任务
*/
//第一层循环
for (;;) {
//获取线程的状态
int c = ctl.get();
int rs = runStateOf(c);
/**
* 该判断条件:
* 1. 如果rs >= SHUTDOWN,则表示此时不再接收新任务;
* 2. 接着判断以下3个条件,只要有1个不满足,则返回false:
* 1. 状态为SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
* 2. firsTask为空
* 3. 阻塞队列不为空
* 首先考虑rs == SHUTDOWN的情况
* 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回false;
* 然后,如果firstTask为空,并且workQueue也为空,则返回false,
* 因为队列中已经没有任务了,不需要再添加线程了
*/
if (rs >= SHUTDOWN && !
(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
// 第二层循环
for (;;) {
// 获取线程数
int wc = workerCountOf(c);
/**
* 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),方法直接返回false;
* 这里的core是addWorker方法的第二个参数,如果为true表示根据corePoolSize来比较,如果为false则根据maximumPoolSize来比较。
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//尝试增加workerCount,如果成功,则跳出第一个for循环,到最外层逻辑上
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果增加workerCount失败,则重新获取ctl的值
c = ctl.get();
// 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 根据firstTask来创建Worker对象,每一个Worker对象都会创建一个线程
w = new Worker(firstTask);
final Thread t = w.thread;
/**
* 如果过线程不为空,则试着将线程加入工作队列中
*/
if (t != null) {
//获取重入锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 重新获取线程的状态
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,将该worker对象添加其中
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;
}
1.1.1 addWorkerFailed()
刚才说到如果添加工作线程失败,会调动addWorkerFailed()
方法,处理添加工作线程失败后的事情,主要目的是从works
的hashSet集合中移除已经添加的工作线程。
/**
* 工作线程创建失败时回退线程
* 1. 从工作线程hashSet中移除工作线程
* 2. workCount减1
* 3. 尝试中断线程
* @param w
*/
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
上面的第二步及第三步主要调用以下方法:
1.1.2 decrementWorkerCount()
/**
* 将workerCount数减1
*/
private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
1.1.3 tryTerminate()
/**
* 尝试将线程池状态改成TERMINATED
* 什么情况下会将状态转成TERMINATED呢?
* 1. 调用shutDown方法或者线程池为空
* 2. 调用stop()方法或者线程池为空
*/
final void tryTerminate() {
for (;;) {
int c = ctl.get();
/**
* 当前线程池的状态为以下几种情况时,直接返回:
* 1. RUNNING,因为还在运行中,不能停止;
* 2. TIDYING或TERMINATED,因为线程池中已经没有正在运行的线程了;
* 3. 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();
// 这里尝试设置状态为TIDYING,如果设置成功,则调用terminated方法
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
// 设置状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
2. shutdown()
该方法主要实现了ExecutorService
接口中的shutdown()
,主要目的是开始有序关闭线程池。在关闭中先执行之前已经提交的任务、但是不接收新任务,如果线程池已经关闭,调用也不会有任何影响。注意:该方法不会等之前已提交的任务执行完成再关闭
/**
* 思路:
* 1. 获取锁
* 2. 检查是否允许关闭
* 3. 将state状态更新值SHUTDOWN
* 4. 中断空闲线程
* 5. 真正的关闭线程
* 6. 释放锁
* 7. 尝试将线程池状态改成TERMINATED
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 判断是否允许关闭线程,主要是做安全检查
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
该方法本身也是调用五个方法来实现关闭功能,下面主要分析五个方法
2.1 advanceRunState(int targetState)
将状态更新至目标状态(SHUTDOWN or STOP),主要给shutdown()及shutdownnow()调用
思路:死循环更新状态
- 获取当前ctl值
- 判断当前ctl是否大于目标状态(targetState),如果是,直接调出循环,否则进入第三步
- 尝试将当前状态更新为目标状态,如果成功则退出,不成功,则循环重试。
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
2.2 interruptIdleWorkers()
中断空闲线程,该方法本身是调用interruptIdleWorkers(boolean onlyOne)
实现中断空闲线程功能。因此主要分析interruptIdleWorkers(boolean onlyOne)
方法。
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
interruptIdleWorkers(boolean onlyOne)
方法主要思路:
如果传入的参数onlyOne为true,表示最多中断一个工作线程。
该方法只有tryTerminate()方法中才调用,此时线程中有其他线程能够被终止
思路:
1. 获取mainLock锁
2. 遍历所有工作任务,如果工作任务所在的线程没有被中断,并且获取锁成功,则中断该线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
2.3 tryTerminate()
尝试将线程池状态改成TERMINATED
什么情况下会将状态转成TERMINATED呢?
- 调用shutDown方法或者线程池为空
- 调用stop()方法或者线程池为空
final void tryTerminate() {
for (;;) {
int c = ctl.get();
/**
* 当前线程池的状态为以下几种情况时,直接返回:
* 1. RUNNING,因为还在运行中,不能停止;
* 2. TIDYING或TERMINATED,因为线程池中已经没有正在运行的线程了;
* 3. 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();
// 这里尝试设置状态为TIDYING,如果设置成功,则调用terminated方法
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
// 设置状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
3. shutdownNow()
该方法主要实现了ExecutorService
接口中的shutdownnow()
,尝试停止所有活跃的正在执行的任务,停止所有等待中的任务。shutdownNow
和shutdown
方法处理思路差不多,第二步将线程状态更新至STOP
状态,而不是SHUTDOWN
状态。主要区别在第二步、第三步、并且增加了第四步移除等待队列中所有的线程。
思路如下:
- 检查线程是否可以中断
- 将工作线程状态更新至STOP
- 中断所有的线程,而不是指只中断空闲线程
- 删掉等待队列中的所有线程,将其加入到一个新的空list中并返回。
- 释放锁
- 尝试将线程池状态改成TERMINATED
/**
* 尝试停止所有活跃的正在执行的任务,停止所有等待中的任务
* @return 返回所有的处于等待状态的任务,并且这些等待的任务最后会从阻塞队列中移除
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 将工作线程状态更新至STOP
advanceRunState(STOP);
// 中断所有的线程,而不是指只中断空闲线程
interruptWorkers();
// 同时移除等待队列中所有的线程
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
3.1 interruptWorkers()
中断所有的线程,包括活跃的线程
/**
* 1. 先获取mainLock锁,然后依次中断工作线程
*/
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
/**
* 如果已经启动,尝试中断
*/
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
3.2 drainQueue()
删掉等待队列中的所有线程,将其加入到一个新的空list中并返回。
/**
* 一般使用阻塞队列的drainTo()方法,如果失败,则手动移除、添加
* @return 返回所有删除的工作线程
*/
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
2. awaitTermination()
该方法同样实现了ExecutorService
接口中的awaitTermination()
,目的是在所有任务执行之前,线程处于等待状态,阻塞线程。
/**
* 调用shutdown(),让所有任务执行完之前,线程都会阻塞。
* @param timeout 超时时间
* @param unit 时间单位
* @return
* @throws InterruptedException
*/
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
4.总结
至此,线程池核心类ThreadPoolExecutor
的主要用途及核心源码分析都已经结束,可以看到该类主要是实现了AbstractExecutorService
、ExecutorService
、Executor
中的关键方法,其实Worker
工作线程主要实现AQS中的一些方法,利用AQS
中定义的state
表示线程是否被使y用,如果想要了解线程池的源码,不建议直接一头扎进ThreadPoolExecutor
源码中,而是从AQS
、Executor
框架多个类,一个一个熟悉,明白彼此之间的逻辑,最终线程池终将会吃透。