系列文章
- 线程池系列 - (1)背景介绍
- 线程池系列 - (2)线程池的状态
- 线程池系列 - (3)拒绝策略
- 线程池系列 - (4)工作流程
- 线程池系列 - (5)shutdown && shutdownNow
- 线程池系列 - (6)submit
shutdown && shutdownNow
在线程池的状态一节中。知道了线程池的5种状态。如👇图所示
也知道了其转换过程 如👇 所示
具体来看一下,这两个的区别以及实现原理
区别
对比 | shutdown | shutdownNow |
---|---|---|
返回值 | void | List<Runnable> |
相同点 | 阻止新来的任务提交 | 阻止新来的任务提交 |
不同 | 对已经提交了的任务不会产生任何影响 | 会中断当前正在运行的线程 |
shutdown
分析一下shutdown
源码。看一下他是如何执行的。依然跟着流程图去分析。看一下线程池是如何通过shutdown
改变其状态的。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 上锁,确保同一时间只能有一个线程执行此操作
mainLock.lock();
try {
// 检查方法调用方是否用权限关闭线程池以及中断工作线程
checkShutdownAccess();
// 将线程池运行状态设置为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断所有空闲线程
interruptIdleWorkers();
// 此方法在ThreadPoolExecutor中是空实现,具体实现在其子类ScheduledThreadPoolExecutor
// 中,用于取消延时任务。
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试将线程池置为TERMINATED状态
tryTerminate();
}
上面我们看到其主要就分成以下几个部分
- 上锁
- checkShutdownAccess
- advanceRunState(SHUTDOWN)
- interruptIdleWorkers()
- onShutdown()
- 解锁
- tryTerminate
抛弃上锁解锁。分析其重要的几个方法。作用以及实现。
advanceRunState 将线程池运行状态设置为SHUTDOWN
重要的是在线程池的状态一节说过。
线程池通过 ctlOf
方法更新线程池状态和线程数量。这里就是通过此方法将线程池的状态 从 Running 改成 SHUTDOWN
private void advanceRunState(int targetState) {
// 程序循环执行,直到将运行状态设为目标值
for (;;) {
// 获取AtomicInteger变量中的整数值
int c = ctl.get();
// if条件的前半部分是判断当前运行状态是否大于等于给定值
if (runStateAtLeast(c, targetState) ||
// 后半部分利用CAS操作将运行状态设置为目标值,成功的话会返回true,失败则返回false
// 👇 核心方法
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
interruptIdleWorkers 中断所有空闲线程
// 下面第二个方法的重载,将参数设为false,也就是中断所有空闲线程
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
重点在于这个入参 onlyOne
字面意思
- false,中断所有空闲线程
- true,则只中断一个空闲线程
通过源码可以知道其中断线程使用的是线程的interrupt()
方法。
对于线程不不理解的小伙伴可以参考笔者的另一个文章线程总结.对线程有很详细的说明
// 中断空闲线程,如果参数为false,中断所有空闲线程,如果为true,则只中断一个空闲线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 如果线程还未中断且处于空闲状态,将其中断。if条件的前半部分很好理解,后半部分 w.tryLock() 方法
// 调用了tryAcquire(int)方法 (继承自AQS),也就是尝试以独占的方式获取资源,成功则返回true,表示
// 线程处于空闲状态;失败会返回false,表示线程处于工作状态。
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
// 如果为true,表示只中断一个空闲线程,并退出循环,这一情况只会用在tryTerminate()方法中
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
在其中断线程的地方看一下去判断是否可以中断线程的判断逻辑。有两处
!t.isInterrupted()
首先要满足线程没有被打断的条件
w.tryLock()
重点看一下这个方法,此方法继承自AQS
,也就是尝试以独占的方式获取资源,成功则返回true,失败则返回false
换句话说。如果一个任务已经执行了。那么就不会进入中断的逻辑。就保证了其对已经提交了的任务不会产生任何影响
的作用
tryTerminate 尝试将线程池置为 TERMINATED 状态
这一步也是最后一步。就是想线程池的状态从 SHUTDOWN 改成 TERMINATED
来看一下其内部实现
final void tryTerminate() {
for (;;) {
// 获取ctl中的整型值
int c = ctl.get();
// 线程池处于RUNNING状态
if (isRunning(c) ||
// 线程池处于TIDYING状态或TERMINATED状态
runStateAtLeast(c, TIDYING) ||
// 线程池处于SHUTDOWN状态但是任务队列不为空
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 执行到这里说明 当前线程池的状态是 `STOP` 或者 `SHUTDOWN`
// 当线程池中的线程数量不是0 。就是说有工作的线程
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 将线程池状态改成 `TIDYING`
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
}
}
通过上面的源码分析。可以知道。使用的还是 ctlOf
去更改状态。
- 先改成 TIDYING 状态
- 在改成 TERMINATED 状态
shutdownNow
对比之前的 shutdown
是非常相似的。
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();
}
// 尝试将线程池置为TERMINATED状态
tryTerminate();
return tasks;
}
interruptWorkers 中断所有线程,包括正在运行的线程
对比到shutdown
来说,他更加直接。直接中断所有线程。不存在判断是否线程已经执行了。
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 中断所有线程
for (Worker w : workers)
// 调用内部类Worker自身方法中断线程
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
通过源码能够知道他中断线程的方式和 shutdown
是一样的。都是使用 interrupt
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
drainQueue 将未执行的任务移入列表中
此方法是将队列中的任务保存到数组中。
private List<Runnable> drainQueue() {
//将队列中的 Runnable 转移到 taskList
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
//移除此队列中所有可用的元素,并将它们添加到给定 collection 中
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}