我们常说非核心线程空闲超时之后就会被销毁,线程池又是如何判断线程是否空闲的呢?在调用了shutdown()或者shutdownNow()线程池内部又是如何变化的,将线程池进行逐步关闭的呢?
1、shutdown()
关闭流程分析
假设此时线程池正常运行,核心线程为5,最大线程数为10。此时达到最大线程数,任务队列中还有10个任务
java
public void shutdown() {
final ReentrantLock mainLock = this.mainLock; // 获取线程池的主锁
mainLock.lock(); // 加锁(保证原子性)
try {
checkShutdownAccess(); // 权限检查
advanceRunState(SHUTDOWN); // 推进状态到 SHUTDOWN
interruptIdleWorkers(); // 中断空闲工作线程
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate(); // 尝试终止线程池
}
源码如上所示,设置线程池状态然后中断空闲线程。最后尝试关闭线程池。 interruptIdleWorkers()
会调用此方法,传入参数为false
java
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();
}
}
循环中的w.tryLocak
若为真,代表此线程此时没有执行任务处于空闲状态。因为线程池在执行任务时会进行加锁(详细流程可以看我前面那篇将线程池工作流程的文章)
尝试关闭线程池的tryTerminate()
源码如下
java
final void tryTerminate() {
for (; ; ) {
int c = ctl.get();
//当处在以下三种状态时方法提前返回
//1.线程池还在运行
//2.状态已经达到或超过TIDYING
//3.未达到STOP状态且任务队列非空,还存在未处理的任务
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateLessThan(c, STOP) && !workQueue.isEmpty()))
return;
//仍然有活跃线程
if (workerCountOf(c) != 0) { // Eligible to terminate
//仅中断一个线程
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// CAS 更新状态为 TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 执行终止钩子
terminated();
} finally {
// 更新为 TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 通知等待线程(如 awaitTermination())
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
若当前线程池还有线程,则中断一个空闲线程后返回。若没有线程则将线程池设为terminated
关闭状态。
在上面所说的情况下,只会对正在获取任务的线程进行中断。线程池中的线程都处于正在执行任务或获取任务。如果提交的任务中没有设置响应中断,那么只会在获取任务的过程中进行响应中断,因为线程池的runwork()
中并没有获取中断响应。
线程池中的线程获取任务的部分源码如下所示
java
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 取不到任务的时候timedOut = true
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
poll()
和take()
会响应中断抛出异常并清除中断状态,然后被捕获,将timeOut设为false,再次进行下一次任务获取。所以即使调用了shutdown()方法仍然会处理已提交的任务,直到线程池中的任务对列为空,对线程进行销毁操作。 获取任务中还有这样一段代码
java
// 当前状态大于等于SHUTDOWN且(大于等于STOP状态或者任务队列为空)
if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
//工作线程数wc减去1,然后直接返回null
decrementWorkerCount();
return null; // 获取到的任务为Null,进行销毁处理
}
在返回为null之后runworker()
会跳出循环,执行processWorkerExit(w, completedAbruptly);
在processWorkerExit会再次调用tryTerminate()
方法对线程状态进行设置为关闭状态。所以对于我们一开始所说的那种情况下的shutdown()关闭流程为:
- 所有线程在获取任务时被中断,然后在poll()时被清除中断重新进行下一次任务的获取,直到任务队列为0。
- 当任务队列的最后一个任务被取走时为空,此时再有线程执行完任务再取获取时,减少线程池中的线程个数并返回Null
- 返回null进行线程销毁,并调用
tryTerminate()
尝试关闭线程池 - 直到最后一个线程在获取任务时返回null,会在
tryTerminate()
将线程池设为关闭状态。至此线程池完全关闭。
2、shutdownNow()
关闭流程分析
java
public List<Runnable> shutdownNow() {
List<Runnable> tasks; // 存储未执行的任务
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); // 权限检查
advanceRunState(STOP); // 推进状态到 STOP
interruptWorkers(); // 中断所有工作线程
tasks = drainQueue(); // 排空任务队列并返回未执行任务
} finally {
mainLock.unlock();
}
tryTerminate(); // 尝试终止线程池
return tasks; // 返回未执行的任务列表
}
会将所有线程设为中断,然后排空任务队列。这样线程池就获取不到任务了,只会执行当前正在执行的任务。同样的,如果提交的任务没有响应中断,那么线程也只会在获取任务的poll方法时响应中断。 假设线程池处于最大线程数10,且都在执行任务,任务队列有20个任务,此时执行shutdownNow()
,关闭流程如下:
- 将线程池设为STOP状态,将所有线程都设为中断状态
- 排空任务队列
- 尝试执行关闭线程池,但因为此时线程池的正在执行的任务还没有执行完,不会进行关闭。
- 各个线程的任务执行完后会再次获取任务,但判断到当前处于STOP状态直接减少线程个数并返回null
- 执行线程销毁操作,每次销毁都会超时关闭线程池
- 当最后一个线程进行销毁时,会将线程池设为关闭状态