如何优雅关闭线程池
许多业务场景中都会到线程池,比如微服务接收外部调用、异步发送邮件、离线统计报表等等。线程池有三个优点:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗;提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行;提高线程的可管理性,对线程进行统一的分配,调优和监控。
老话说,请神容易送神难,使用了线程池就要承受它带来的问题。线程池的运行机制比较复杂,使用线程池的参数并不好配置。线程池执行的情况与任务类型相关,IO密集型和CPU密集型的任务运行起来的情况差异很大,配置合理依赖开发人员的经验和知识。如果配置不当,反而降低系统效率。另外,还一个问题,如何优雅关闭线程池,避免任务或者数据丢失呢?
首先要搞清楚销毁线程池的基本方法。Java线程池ThreadPoolExecutor提供了三个重要的方法shutdown()、shutdownNow()和awaitTermination():
- shutdown:拒绝新任务提交到线程池,待执行与正在执行的任务继续执行。
- shutdownNow:拒绝提交新任务到线程池,取消队列中等待执行的任务,尝试取消正在执行任务。
- awaitTermination(long timeOut, TimeUnit unit):阻塞线程,等待所有待执行以及正在执行的任务执行完成,或者等到超时,或者线程被中断抛出中断异常,然后返回true或false(表示已超时)
这三个方法的区别是:
- shutdown与shutdownNow之后都拒绝提交任务,awaitTermination可以继续提交任务。
- awaitTermination是阻塞的,返回结果是线程池是否已停止(true/false)shutdown/Now不阻塞。
- shutdownNow是立即关闭,而shutdown是优雅关闭。
确定优雅关闭线程的步骤:
(1)执行shutdown方法,等待所有任务执行完毕并拒绝新任务的提交。
(2)执行awaitTermination(long timeout,TimeUnit unit),指定超时时间,判断是是否已经关闭所有任务,防止线程永远无法关闭。
(3)如果第2步返回fasle,或者被中断。调用shutDownNow方法立即关闭线程池所有任务。
根据这个步骤定义通用关闭线程池工具类:
//这个工具类来自dubbo源码
public class ExecutorUtil {
private static final Logger logger = LoggerFactory.getLogger(ExecutorUtil.class);
private static final ThreadPoolExecutor shutdownExecutor = new ThreadPoolExecutor(0, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(100),
new NamedThreadFactory("Close-ExecutorService-Timer", true));
public static boolean isTerminated(Executor executor) {
if (executor instanceof ExecutorService) {
if (((ExecutorService) executor).isTerminated()) {
return true;
}
}
return false;
}
/**
* Use the shutdown pattern from:
* https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
* @param executor the Executor to shutdown
* @param timeout the timeout in milliseconds before termination
*/
public static void gracefulShutdown(Executor executor, int timeout) {
if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
return;
}
final ExecutorService es = (ExecutorService) executor;
try {
// Disable new tasks from being submitted
es.shutdown();
} catch (SecurityException ex2) {
return;
} catch (NullPointerException ex2) {
return;
}
try {
// Wait a while for existing tasks to terminate
if (!es.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
es.shutdownNow();
}
} catch (InterruptedException ex) {
es.shutdownNow();
Thread.currentThread().interrupt();
}
if (!isTerminated(es)) {
newThreadToCloseExecutor(es);
}
}
public static void shutdownNow(Executor executor, final int timeout) {
if (!(executor instanceof ExecutorService) || isTerminated(executor)) {
return;
}
final ExecutorService es = (ExecutorService) executor;
try {
es.shutdownNow();
} catch (SecurityException ex2) {
return;
} catch (NullPointerException ex2) {
return;
}
try {
es.awaitTermination(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
if (!isTerminated(es)) {
newThreadToCloseExecutor(es);
}
}
}
1 在ServletContextListener接口中销毁
以Sping Boot工程为例,ServletContextListener接口是Servlet包中的接口,在Tomcat启动时会执行该接口对象的contextInitialized(ServletContextEvent sce)方法,当Tomcat关闭时会调用该接口对象的contextDestroyed(ServletContextEvent sce)方法。启动类中加上@ServletComponentScan注解
@SpringBootApplication
@ServletComponentScan
public class ShutdownThreadPoolApplication {
public static void main(String[] args) {
SpringApplication.run(ShutdownThreadPoolApplication.class, args);
}
}
@WebListener
public class ApplicationWatch implements ServletContextListener {
private static Logger logger = Logger.getLogger(ArchDataInit.class);
public void contextDestroyed(ServletContextEvent event) {
logger.info("start ApplicationWatch contextDestroyed method");
logger.info("start shutdown thread pool");
//传入外部定义的线程池executor
ExecutorUtil.gracefulShutdown(executor, 20);
logger.info("end shutdown thread pool");
}
2 在DisposableBean接口中销毁
在Spring销毁bean时候,如果实现了DisposableBean接口会自动回调destroy方法,在该方法中再销毁线程池。
public class DestroyThreadPoolWhenShutApp implements DisposableBean{
private static Logger logger = Logger.getLogger(DestroyThreadPoolWhenShutApp.class);
public void destroy() throws Exception {
logger.info("start ApplicationWatch contextDestroyed method");
logger.info("start shutdown thread pool");
//传入外部定义的线程池executor
ExecutorUtil.gracefulShutdown(executor, 20);
logger.info("end shutdown thread pool");
}
}
Spring配置文件中创建一个bean级别的销毁bean
<bean id="destroyThreadPool" class="com.demo.spring.DestroyThreadPoolWhenShutApp"/>
本文链接:https://www.codingbrick.com/archives/1106.html
特别声明:除特别标注,本站文章均为原创,转载请注明作者和出处倾城架构,请勿用于任何商业用途。