Tomcat重启过慢问题:线程无法正常停止与JDBC驱动未注销
解决Tomcat服务重启缓慢:JDBC驱动注销与线程正常停止方案
一、正确注销JDBC驱动的两种方式
针对日志中JDBC驱动未正常注销的警告,可通过以下两种方式解决:
1. 通过ServletContextListener在应用停止时主动注销
实现ServletContextListener,在Web应用销毁阶段,注销当前应用类加载器加载的JDBC驱动,避免Tomcat强制清理:
@WebListener public class DriverCleanupListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent sce) { Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); try { // 仅注销当前Web应用类加载器加载的驱动 if (driver.getClass().getClassLoader() == sce.getServletContext().getClassLoader()) { DriverManager.deregisterDriver(driver); } } catch (SQLException e) { // 此处添加日志记录,便于排查注销失败原因 } } } @Override public void contextInitialized(ServletContextEvent sce) { // 初始化逻辑(如无需可留空) } }
2. 将JDBC驱动放入Tomcat全局类路径
把JDBC驱动jar包移至CATALINA_HOME/lib目录下,由Tomcat的系统类加载器负责加载驱动。这种方式下,驱动生命周期由Tomcat全局管理,不会出现Web应用卸载时的驱动注销问题。注意:多Web应用共享驱动时需保证版本一致。
二、线程关闭异常确实会导致Tomcat重启过慢
Tomcat在停止Web应用时,会等待未正常终止的线程达到超时时间(Tomcat 9默认20秒),超时后才会强制销毁线程。如果存在多个未停止的线程,等待时间会累积,直接导致重启耗时变长。
正确停止线程的方案
根据线程类型不同,采取对应的终止方式:
1. 针对java.util.Timer线程
Timer创建的线程默认是非后台线程,应用停止时不会自动终止,需主动调用cancel()方法:
@WebListener public class TimerCleanupListener implements ServletContextListener { private Timer taskTimer; @Override public void contextInitialized(ServletContextEvent sce) { taskTimer = new Timer(); // 示例:每分钟执行一次定时任务 taskTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { // 执行你的业务逻辑 } }, 0, 60000); } @Override public void contextDestroyed(ServletContextEvent sce) { if (taskTimer != null) { taskTimer.cancel(); // 终止Timer线程的循环逻辑 } } }
调用cancel()后,Timer的后台线程会退出mainLoop,正常终止,不会触发Tomcat的线程泄露警告。
2. 针对自定义线程
避免使用已废弃的Thread.stop(),采用中断+状态标记的方式优雅终止线程:
public class CustomWorkerThread extends Thread { // 用volatile保证多线程下的可见性 private volatile boolean isRunning = true; @Override public void run() { // 同时检查运行状态和中断标记 while (isRunning && !Thread.currentThread().isInterrupted()) { try { // 执行业务逻辑 Thread.sleep(1000); // 示例阻塞方法 } catch (InterruptedException e) { // 捕获中断后,重置中断标记并终止循环 Thread.currentThread().interrupt(); isRunning = false; } } } // 对外提供停止方法 public void stopGracefully() { isRunning = false; this.interrupt(); // 唤醒阻塞中的线程 } }
然后在ServletContextListener的contextDestroyed方法中,调用自定义线程的stopGracefully()方法,触发线程终止。
你之前的尝试无效的原因
你仅检查了线程的中断状态,但未在捕获中断或检测到中断后执行退出逻辑,也没有主动触发中断来唤醒处于wait状态的线程(比如TimerThread的wait),因此线程无法正常终止。
内容的提问来源于stack exchange,提问作者Whatever22
相关产品推荐
相关产品推荐

