核心思路
- 项目启动时创建全局单例线程池
- 实现
ServletContextListener - 停机时调用线程池的优雅关闭方法 :
shutdown()awaitTermination(...)等待任务执行完
- 超时未关闭则强制关闭
shutdownNow()
完整实现代码
1. 线程池单例(建议放在工具类)
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TaskPool {
// 全局唯一线程池
private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(
5,
10,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
public static ExecutorService getExecutor() {
return EXECUTOR;
}
}
- 编写监听器(关键)
java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
@WebListener // 让 Tomcat 自动识别
public class ThreadPoolShutdownListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 应用启动时可做初始化
System.out.println("Tomcat 应用启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Tomcat 停机,开始关闭线程池...");
ExecutorService executor = TaskPool.getExecutor();
// 1. 拒绝新任务
executor.shutdown();
try {
// 2. 等待已提交任务执行完毕(例如等30秒)
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println("线程池超时未关闭,强制关闭");
// 3. 超时则强制关闭
executor.shutdownNow();
}
} catch (InterruptedException e) {
System.err.println("关闭线程池被中断");
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("线程池已安全关闭");
}
}
- 如果不用 @WebListener,就在 web.xml 配置
XML
<listener>
<listener-class>com.xxx.xxx.ThreadPoolShutdownListener</listener-class>
</listener>
为什么这样能保证 Tomcat 等任务执行完?
- Tomcat 停止时会按顺序调用所有 ServletContextListener 的 contextDestroyed
awaitTermination(30, SECONDS)会阻塞当前线程 ,直到:- 所有任务执行完
- 或超时
- 阻塞期间 Tomcat 不会继续销毁 Filter、Servlet,实现等待效果
重要注意点
- 不要用 Executors.newFixedThreadPool 队列无界,可能堆积太多任务导致停机很久。
- awaitTermination 时间要合理 一般 10~60 秒,根据你的任务耗时设置。
- 你的任务最好能响应中断 否则
shutdownNow()也杀不掉,会一直卡到超时。 - 不要在定时任务 / 异步里无限循环否则永远关不掉。
参考文献:豆包