线程池shutdown流程以及线程的销毁
前文: 线程池的execute
流程图
shutDown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查权限, 是否可以终止线程池
checkShutdownAccess();
// 自旋设置线程池状态为SHUTDOWN
// 在shutDownNow里面这里设置的是STOP, 并且会返回一个Runnable的List
advanceRunState(SHUTDOWN);
// 将workers集合中的所有线程标记为interrupt
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
在interruptIdleWorkers也是要获取mainLock锁的, 所以这里是保证只有同一线程进入
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 这里获取w的锁失败就表示w还有任务正在运行
// 只有获取到任务的线程才会上锁防止被interrupt
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
接下来我们看看shutdown是如何执行完阻塞队列中任务才销毁线程的, 我们可以看见getTask中如果返回的是null, 那么在runWorker中的自旋也会结束, 当线程的run方法运行完毕之后, 就会终止
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 如果使用的是shutDownNow这里就会直接返回null
// 如果是shutDown那么阻塞队列为空时才会返回null, 即执行完毕阻塞队列中的任务
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 省略
}
}
我们知道在设置线程池状态为SHUTDOWN之后 execute方法就无法添加新任务给线程池, 所以SHUTDOWN了之后, 阻塞队列中的任务不会再添加, 执行完现有任务之后线程池就会关闭
不论是shutDown还是shutDownNow正在执行的任务都会被执行完毕, 因为在设置interrupt的时候需要先获取worker的锁, 如果获取失败则会让worker通过自旋的getTask获取null来终止, 如果获取成功那么正在阻塞的线程都会抛出InterruptedException异常然后将终止标记清空继续运行
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
阻塞队列中无任务的时候, 线程就会被阻塞在取阻塞队列任务的地方, 被interrupt抛出异常之后, 这里会捕获到异常, 然后timedOut设置为false重新进行自旋, 之后就会根据上述的线程池状态判断, 返回null
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 {
// 被锁阻塞的线程获取锁之后进入cas操作是一定失败的
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
}
}
后记
很多人可能只知道shutDown和shutDownNow的区别在于前者会执行完毕线程池中的任务后再销毁, 后者是直接销毁, 可是阅读了源码之后才能考虑到更多的问题, 比如阻塞中的线程如何被销毁, 又比如在woker执行任务的时候如何保证不被中断, 都在shutDown的一系列源码中有所体现