ThreadPoolTaskExecutor 优雅关闭
ThreadPoolTaskExecutor 是Spring封装过的线程池,使用其可以在应用下线时,实现一定程度上的优雅关闭。
继承关系
ThreadPoolTaskExecuto
r 实现了ExecutorConfigurationSupport
,而后者实行了DisposableBean
将会在Bean销毁的时候触发从而触发**destroy()**其中执行了 线程池的关闭方法:shutdown()
两种策略
无限制的等待任务完成
如何配置了waitForTasksToCompleteOnShutdown
为 true,那么系统将会等待所有正在执行的任务完成,并且等待队列中的任务也全部执行完成(但不会接受新的任务)
ExecutorConfigurationSupport#shutdown
public void shutdown() {
if (logger.isInfoEnabled()) {
logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
if (this.waitForTasksToCompleteOnShutdown) {
// 发起关闭指令,停止接收新的任务
this.executor.shutdown();
}
else {
// 发出终止指令,不接收新的任务,打断正在执行的任务(不一定真的被打断),取消队列中的任务
for (Runnable remainingTask : this.executor.awaitTerminationIfNecessary()) {
cancelRemainingTask(remainingTask);
}
}
awaitTerminationIfNecessary(this.executor);
}
}
ThreadPoolExecutor#shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查是否由权限关闭线程池
checkShutdownAccess();
// 将线程池状态调整为关闭,关闭后将不会接收新的任务
advanceRunState(SHUTDOWN);
// 打断空闲的工作者,正在工作的工作者不受影响(没有产出的员工先裁掉😂)
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
ThreadPoolExecutor#interruptIdleWorkers
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 如果线程没有被打断,并且成功获取到worker的锁
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
我们知道线程池中的任务实际由worker来完成,worker在开始执行任务时会调用lock()上锁,所以如何此时任务没有完成,这里调用tryLock()将不会获取到锁,所以这里实际上相当于并没有打断任务
有限制的等待一段时间
ExecutorService#shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 将线程池状态修改为终止状态
advanceRunState(STOP);
// 打断所有的工作者(要跑路了,全部干掉😒)
interruptWorkers();
// 取消队列中的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
ExecutorService#interruptWorkers
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 全部worker都打断
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
ExecutorConfigurationSupport#awaitTerminationIfNecessary
private void awaitTerminationIfNecessary(ExecutorService executor) {
if (this.awaitTerminationMillis > 0) {
try {
if (!executor.awaitTermination(this.awaitTerminationMillis, TimeUnit.MILLISECONDS)) {
if (logger.isWarnEnabled()) {
logger.warn("Timed out while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
}
}
catch (InterruptedException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Interrupted while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
Thread.currentThread().interrupt();
}
}
}
等待awaitTerminationMillis
,这个等待的时间需要主动配置,否则不会做任何等待
ps:打算线程即调用线程的interupt()
方法并不代表一定会影响任务的执行,如果线程的状态为running
,实际只会设置一个标记不会真正的中断线程,就算线程 为wait
状态会如果你的程序中没有对InterruptedException异常做捕获,那么实际上正在运行的任务仍然可以继续运行,所以这里的等待还是有一定的意义
实验一下
无限制的等待实验
# 以非阻塞的方式模拟延迟
public static void workTime(long ms) {
final long l = System.currentTimeMillis();
while (System.currentTimeMillis() <= l + ms) {
}
}
public static void main(String[] args) throws InterruptedException {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
executor.execute(()->{
try {
MyUtils.workTime(3000);
log.info("任务1执行完成");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
executor.execute(()->{
try {
MyUtils.workTime(3000);
log.info("任务2执行完成");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
executor.shutdown();
Thread.sleep(100000);
}
这里只设置了一个核心线程,设置了等待所有任务完成再关闭,每个任务执行需要3s,任务1提交后马上执行,任务2方法等待队列,而后马上关闭线程池模拟应用下线场景.
结果1
00:38:55.493 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService
00:38:55.513 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService
00:38:58.514 [ThreadPoolTaskExecutor-1] INFO com.duiba.HkTest - 任务1执行完成
00:39:01.515 [ThreadPoolTaskExecutor-1] INFO com.duiba.HkTest - 任务2执行完成
结果如上,调用shutdown()
后,两个任务都依赖正常完成
有限制的等待实验
// 调用线程的sleep,线程变成wait状态
public static void sleep(long ms) throws InterruptedException {
Thread.sleep(ms);
}
public static void main(String[] args) throws InterruptedException {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setWaitForTasksToCompleteOnShutdown(false);
executor.setAwaitTerminationSeconds(4);
executor.initialize();
executor.execute(()->{
try {
MyUtils.sleep(3000);
log.info("任务1执行完成");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
executor.execute(()->{
try {
MyUtils.sleep(3000);
log.info("任务2执行完成");
} catch (Exception e) {
log.info("任务2:我什么都管就要完成任务");
}
});
executor.execute(()->{
try {
MyUtils.workTime(3000);
log.info("任务3执行完成");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
executor.shutdown();
Thread.sleep(100000);
}
结果2
00:59:28.879 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService
00:59:28.899 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService
00:59:28.899 [ThreadPoolTaskExecutor-2] INFO com.duiba.HkTest - 任务2:我什么都管就要完成任务
Exception in thread "ThreadPoolTaskExecutor-1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at com.duiba.HkTest.lambda$main$0(HkTest.java:38)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.duiba.utils.MyUtils.sleep(MyUtils.java:11)
at com.duiba.HkTest.lambda$main$0(HkTest.java:35)
... 3 more
00:59:31.900 [ThreadPoolTaskExecutor-3] INFO com.duiba.HkTest - 任务3执行完成
结果如上,如果线程处于非阻塞状态,或者压根处理中断异常,即使打算也没有任务作用,任务依旧可以正常完成(在jvm关闭之前)