一、背景:
线程池中有任务正在执行,此时需要关闭或重启应用,池中的任务如何处理,需要考虑任务的损失、关闭速度两个方面考虑。
推荐使用Spring提供的线程池:ThreadPoolTaskExecutor,让Spring帮我们管理线程池的生命周期,初始化、监控、扩展、关闭。
特别在应用关闭、重启时能实现优雅关闭线程池,以使我们的损失降到最低;但前提是配置合理的初始化参数。
二、原理:
在Spring中如果一个Bean类,实现了Disposable接口,并实现了其destroy方法。当Spring在销毁这个类的Bean时,就会执行这个destroy方法。通常destroy方法用于执行一些善后工作,比如资源回收、关闭数据库连接、关闭线程池、回收文件资源等。
Spring提供的线程池:ThreadPoolTaskExecutor类的父类ExecutorConfigurationSupport正好实现了DisposableBean接口:
/**
* Calls {@code shutdown} when the BeanFactory destroys
* the task executor instance.
* @see #shutdown()
*/
@Override
public void destroy() {
shutdown();
}
/**
* Perform a shutdown on the underlying ExecutorService.
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
*/
public void shutdown() {
if (logger.isInfoEnabled()) {
logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
// 是否等待任务全部执行完毕再关闭的开关
if (this.waitForTasksToCompleteOnShutdown) {
// 关闭JDK线程池
this.executor.shutdown();//非阻塞
}
else {
//shutdownNow()的作用:
//1.不再接收新任务
//2.清空任务队列,并返回等待执行的任务,并取消这些任务(要支持取消,就必须实现RunneableFuture接口的cancel方法)
//3.立即中断正在执行的任务(能中断就中断,否则就让其执行完毕)
for (Runnable remainingTask : this.executor.shutdownNow()//非阻塞) {
cancelRemainingTask(remainingTask);
}
}
awaitTerminationIfNecessary(this.executor);
}
}
/**
* Wait for the executor to terminate, according to the value of the
* {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
*/
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();
}
}
}
实践策略:
- 任务优先:保证任务能执行完毕
1.waitForTasksToCompleteOnShutdown设为true。
2.awaitTerminationMillis超时时间设置长一点。
3.任务队列尽量小一点(以提高关闭速度)。
- 速度优先:尽可能快的关闭线程池
1.waitForTasksToCompleteOnShutdown设为false。
2.假如单个任务比较耗时,需要将任务定义为“中断友好”。
3.实现RunneableFuture接口的cancel方法。
4.awaitTerminationMillis设得短一点,即便任务取消失败,也能利用超时机制关闭线程池。
5.任务队列容量小一些,以减小损失。
强制杀死应用进程的情形,Spring也帮不上忙,此时要做到:
标签:Java,shutdown,优雅,任务,线程,关闭,executor,logger From: https://www.cnblogs.com/JaxYoun/p/181642651.做好任务执行记录,方便从断点继续执行。
2.尽量保证任务的幂等性,即便全部重执行,也不会引入副作用。