大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
关闭ThreadPoolExecutor的方法有shutdown()和shutdownNow()方法,本篇文章将对ThreadPoolExecutor的关闭进行分析。
正文
一. shutdown()
首先分析shutdown()方法,其实现如下。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 循环通过CAS方式将线程池状态置为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲Worker
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
// 尝试终止线程池
tryTerminate();
}
在shutdown()方法中首先会将线程池状态置为SHUTDOWN,然后调用interruptIdleWorkers()方法中断空闲Worker,最后调用tryTerminate()方法来尝试终止线程池。那么这里要解释一下什么是空闲Worker,先看一下interruptIdleWorkers()的实现。
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 中断线程前需要先尝试获取Worker的锁
// 只能获取到空闲Worker的锁,所以shutdown()方法只会中断空闲Worker
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
调用interruptIdleWorkers()方法中断Worker前首先需要尝试获取Worker的锁,已知Worker除了实现Runnable接口外,还继承于AbstractQueuedSynchronizer,因此Worker本身是一把锁,然后在runWorker()中Worker执行任务前都会先获取Worker的锁,这里看一下Worker的lock()方法的实现。
public void lock() {
acquire(1);
}
protected boolean tryAcquire(int unused) {
// 以CAS方式将state从0设置为1
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
可以发现,Worker在lock()中调用了acquire()方法,该方法由AbstractQueuedSynchronizer抽象类提供,在acquire()中会调用其子类实现的tryAcquire()方法,tryAcquire()方法会以CAS方式将state从0设置为1,因此这样的设计让Worker是一把不可重入锁。回到interruptIdleWorkers()方法,前面提到该方法中断Worker前会尝试获取Worker的锁,能够获取到锁才会中断Worker,而因为Worker是不可重入锁,所以正在执行任务的Worker是无法获取到锁的,只有那些没有执行任务的Worker的锁才能够被获取,因此所谓的中断空闲Worker,实际就是中断没有执行任务的Worker,那些执行任务的Worker在shutdown()方法被调用时不会被中断,这些Worker执行完任务后会继续从任务阻塞队列中获取任务来执行,直到任务阻塞队列为空,此时没有被中断过的Worker也会被删除掉,等到线程池中没有Worker以及任务阻塞队列没有任务后,线程池才会被终止掉。
对于shutdown()方法,一句话总结就是:将线程池状态置为SHUTDOWN并拒绝接受新任务,等到线程池Worker数量为0,任务阻塞队列为空时,关闭线程池。
二. shutdownNow()
现在再来分析shutdownNow()方法。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 循环通过CAS方式将线程池状态置为STOP
advanceRunState(STOP);
// 中断所有Worker
interruptWorkers();
// 将任务阻塞队列中的任务获取出来并返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试终止线程池
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 中断线程池中所有Worker
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
在shutdownNow()方法中首先会将线程池状态置为STOP,然后调用interruptWorkers()方法中断线程池中的所有Worker,接着调用tryTerminate()方法来尝试终止线程池,最后shutdownNow()方法会将任务阻塞队列中还未被执行的任务返回。shutdownNow()方法调用之后,线程池中的所有Worker都会被中断,包括正在执行任务的Worker,等到所有Worker都被删除之后,线程池即被终止,也就是说,shutdownNow()不会保证当前时刻正在执行的任务会被安全的执行完,并且会放弃执行任务阻塞队列中的所有任务。
三. tryTerminate()
关于线程池的关闭,还有一个重要的方法,那就是前面多次提到的tryTerminate()方法,该方法能确保线程池可以被正确的关闭,其实现如下所示。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 如果线程池状态为RUNNING,则没有资格终止线程池
// 如果线程池状态大于等于TIDYING,则没有资格终止线程池
// 如果线程池状态为SHUTDOWN但任务阻塞队列不为空,则没有资格终止线程池
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 线程池状态为SHUTDOWN且任务阻塞队列为空会执行到这里
// 线程池状态为STOP会执行到这里
// Worker数量不为0,表明当前还有正在执行任务的Worker或者空闲的Worker,此时中断一个空闲的Worker
// 在这里被中断的空闲Worker会在getTask()方法中返回null,从而执行processWorkerExit(),最终该Worker会被删除
// processWorkerExit()方法中又会调用tryTerminate(),因此将shutdown信号在空闲Worker之间进行了传播
if (workerCountOf(c) != 0) {
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();
}
}
}
在tryTerminate()方法的官方注释中给出了两种线程池会被终止的情况:
- 线程池的状态为SHUTDOWN,
Worker数量为0,任务阻塞队列为空; - 线程池的状态为STOP,
Worker数量为0。
官方注释中还说明在所有可能导致线程池终止的操作中都应该调用tryTerminate()方法来尝试终止线程池,因此线程池中Worker被删除时和任务阻塞队列中任务被删除时会调用tryTerminate(),以达到在线程池符合终止条件时及时终止线程池。
总结
关闭ThreadPoolExecutor有两种方式,总结如下。
shutdown()。调用shutdown()方法会首先将线程池状态置为SHUTDOWN并拒绝接受新任务,然后中断空闲Worker,等到线程池中Worker数量为0,任务阻塞队列为空时,线程池被真正关闭;shutdownNow()。调用shutdownNow()方法会首先将线程池状态置为STOP,然后中断所有Worker(包括正在执行任务的Worker),并将任务阻塞队列中还未被执行的任务返回,当线程池Worker数量为0时,线程池被真正关闭。
还有一点需要说明,Worker除了实现Runnable接口外,还继承于AbstractQueuedSynchronizer,因此Worker本身是一把锁,Worker执行任务前都会先获取Worker的锁,所以正在执行任务的Worker的锁是无法被获取的,换言之,只有没有执行任务的Worker的锁才能被获取,这些Worker就称为空闲Worker。
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
文章详细分析了Java中ThreadPoolExecutor的关闭方法,包括shutdown()和shutdownNow()。shutdown()方法会将线程池状态设为SHUTDOWN,中断空闲工作线程,等待所有任务执行完毕后关闭;而shutdownNow()则会立即停止所有工作线程,尝试中断正在执行的任务,并返回未执行的任务列表。tryTerminate()方法在合适的时机尝试终止线程池。
3万+

被折叠的 条评论
为什么被折叠?



