线程池状态转换流程
上一篇文章介绍了线程池的五种状态各种的含义,今天来介绍这五种状态是怎么流转的,还是看线程池的源码,首先来一张线程池状态流转图
线程池各状态转换
RUNING ——> SHUTDOWN:当调用线程池的 shutdown 方法时状态变为SHUTDOWN;
RUNNING ——>STOP:当调用线程池的 shutdownNow 方法时状态变为STOP;
SHUTDOWN ——> TIDYING:当线程池的 shutdown 方法调用时,会再调用 tryTerminated 方法,将状态改为 TIDYING 状态;
STOP ——> TIDYING:当线程池的 shutdownNow 方法调用时,会再调用 tryTerminated 方法,将状态改为 TIDYING 状态;
TIDYING——>TERMINATED:当 tryTerminated 调用时,执行完 terminated 方法后,会将线程池的状态改为 TERMINATED 状态;
源码分析
RUNNING:
线程池初始的默认状态就是 RUNNING 状态;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
这个在上一篇介绍过的,它是记录线程池状态和线程池线程数量的一个复合类型的变量;
可以看到这个属性初始化是,线程池的状态就是 RUNNING 的;
SHUTDOWN:
当线程池执行完 shutdown 方法之后就会变为 SHUTDOWN 状态;
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
1、checkShutdownAccess 方法:
这个方法就是使用 SecurityManager 类做了一些权限校验,就不介绍了;
2、advanceRunState(SHUTDOWN) 方法:
这个方法从方法名可以知道,它只是预先将线程池的状态设置为 SHUTDOWN状态,具体看源码;
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
我们看这个方法就是通过循环重试的方式将线程池的状态改为 SHUTDOWN;
先获取 ctl 的值,如果线程池的状态最少是 SHUTDOWN 则退出循环,否则通过 CAS 的方式将其状态设置为 SHUTDOWN 状态,设置成功后退出循环;
这里解释一下为什么最少是 SHUTDOWN ?可以参考之前介绍线程池状态的文章;因为线程池的状态是递增的,如果状态大于或等于 SHUTDOWN 则说明线程已经调用过 shutdown 或者 shutdownNow 方法因此直接退出循环;
3、interruptIdleWorkers() 方法:
通过方法名就可以知道它是用来停止线程的方法,具体看源码:
private void interruptIdleWorkers() {
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();
}
}
} finally {
mainLock.unlock();
}
}
因为这个方法需要操作 workers 所以需要加锁;
具体做法也很简单就是循环 worker 设置线程池中线程的中断标识;
这里有个需要解释的就是 w.tryLock() 的调用,也就是说这个方法只给能给 worker 上锁的线程设置中断标识(获取锁失败说明线程正在执行任务,因此不会被中断),这就是 shutdown 和shutdownNow 本质区别的地方了;
shutdown 方法执行后,它不会接受新任务了,但是它不会抛弃队列的任务,它会继续将任务执行完,因此他是一种温和的停止线程池的方式;
4、onShutdown() 方法:
这个方法在源码中是个空方法就不贴源码了,可以看上面的注视,这个方法在 ScheduledThreadPoolExecutor 中有实现,就是做一些清理类的工作,这个方法其实是给线程池相关其他工具用户的一个钩子函数,主要做一些关闭线程池后的清理工作;
5、tryTerminate() 方法:
这个方法是改变线程池状态的方法,这个方法就在后面讲解吧;
STOP
当线程池在执行完 shutdownNow 方法后,就会将状态从 RUNNING 改为 STOP 状态;
public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
1、checkShutdownAccess 方法:
这个方法就是在上面已经介绍过了,也是做权限校验的;
2、advanceRunState(STOP) 方法:
这个方法从也介绍过,它是将线程池的状态改成 STOP 状态;
3、interruptWorkers() 方法:
和上面的方法类似也是用来停止线程的,只是略有不同,具体看源码;
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) {
}
}
}
interruptWorkers 这个方法很简单,就是循环调用 worker 里的 interruptIfStarted 方法停止线程;
我们看 interruptIfStarted 方法,这个方法会停止 state 大于等于 0 的所有线程;
而对于 worker 来说,它实现了 AQS 类,状态 1 表示锁被占用,0 表示锁被释放,因此大于等于 0 就代表了所有的线程;
因此 interruptWorkers 方法会停止所有的线程,包括正在活动中的线程;
着就是 shutdownNow 和 shutdown 方法的根本区别了;
shutdownNow方法被调用后,它除了不接受新的任务外,它会停止所有的任务,正在执行的也会被中断,而且会抛弃队列中的任务
4、drainQueue() 方法:
这个方法会将线程池队列中还未执行完的任务放到另一个列表中并返回;
private List drainQueue() {
BlockingQueue q = workQueue;
ArrayList taskList = new ArrayList();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
这个方法很简单就是把线程池阻塞队列中的任务都放到一个 ArrayList 的列表中,并将它返回给调用方,线程池中的阻塞队列就会被清空了;
因此这个就是 shutdownNow 很重要的一个部分,会抛弃掉线程池队列中所有的任务;
5、tryTerminate() 方法:
这个方法在下面在介绍,因为它和线程池的另外两种状态有关;
TIDYING & TERMINATED
这两种状态都是在调用 tryTerminate 方法之后被设置的,下面着重来介绍 tryTerminate 方法;
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
a、这个方法首先会进行一些校验的工作;
首先会校验线程池当前的状态,如果线程池正在运行,或者已经停止并且队列中的任务为空就直接返回;
然后在校验队列中的任务,如果队列中还有任务,会尝试将池中非活动的线程给关闭,然后返回;
b、校验工作完成后,才是开始真正改变线程池的状态了;
然后会尝试将线程池的状态改为 TIDYING 状态,成功后会进行下面的操作,由于这个是个 for 循环,因此使用 CAS 修改线程池状态失败后,会重试;
修改线程池状态为 TIDYING 成功后,会执行 terminated 方法,这个方法其实在线程池中是一个空方法,没有任何的实现;
其实 terminated 方法是线程池留给继承它的其他类,来进行一些线程池关闭后的资源清理等工作的;
c、当将线程池状态由改为 TIDYING 并且 terminated 执行完后;
当 terminated 方法执行后,一定会将线程池的状态由 TIDYING 改为 TERMINATED;
最后会执行 termination 条件变量的 signalAll 方法,会唤醒所有被 termination 变量阻塞的线程;
关于 termination 条件变量,则需要看下面的 awaitTermination 方法了
首先来看看 termination 条件变量
private final Condition termination = mainLock.newCondition();
可以看到 termination 是线程池主锁 mainLock 的一个条件变量,接下来看具体的方法;
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();
}
}
这个方法也是一个 for 循环,其主要的部分就是会调用 termination 条件变量的 awaitNanos 方法;
这个方法会在线程池状态是TERMINATED 时返回,也会在超时时间到达时返回;
再回到刚刚的 tryTerminated 最后的部分,也就是说当有线程调用 awaitTermination 方法后,会被阻塞挂起,但是只要线程池变成 TERMINATED 或着超时时间到达后,当前被挂起的线程才会返回;
总结
最后总结一下线程池各状态的转换过程,其实最重要的就是两个关闭线程池的方法;
shutdown 方法,当调用后,线程池不会接受新的任务,但是会继续执行队列中的任务,也不会停止正在执行任务的线程,因此这是一种温和的关闭线程池的方式;
shutdownNow 方法,当调用后,线程池不会接受新的任务,并且会直接抛弃队列中的任务,还会直接停止正在执行任务的线程,因此这是一种激进的关闭线程池的方式;