Tomcat关闭时因ScheduledExecutorService守护线程挂起问题求助
Hey there, let's figure out why your Tomcat is hanging on shutdown even after switching from extending Thread to ScheduledExecutorService—the root issue almost always boils down to not properly shutting down your executors/threads when the Servlet is destroyed, leaving non-daemon threads running that Tomcat waits for indefinitely.
Why Your Previous Approaches Failed
- Inheriting Thread: If you didn't explicitly interrupt or stop the thread in
destroy(), Tomcat would wait for all non-daemon threads to finish before shutting down. UsingThread.stop()is unsafe and deprecated anyway, so that's not a viable fix. - ScheduledExecutorService Without Proper Shutdown: Simply creating the executor in
init()isn't enough. If you skip properly shutting it down indestroy(), the executor's threads keep running, forcing Tomcat to hit its shutdown timeout and freeze.
Step-by-Step Fix
Here's how to correctly set up and tear down your ScheduledExecutorService in a Servlet, ensuring Tomcat shuts down cleanly:
1. Initialize the Executor in init()
Create the executor with a named thread factory (for easier debugging) and make sure your tasks handle interrupts:
private ScheduledExecutorService scheduler; @Override public void init() throws ServletException { // Create a single-threaded executor with a named thread scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { Thread thread = new Thread(runnable); thread.setName("My-Scheduled-Task-Thread"); // Name helps in log debugging thread.setDaemon(false); // We'll handle shutdown manually instead of relying on daemon threads return thread; }); // Schedule your recurring task (example: run every 10 seconds) scheduler.scheduleAtFixedRate(() -> { try { // Check if the thread has been interrupted before running the task if (Thread.currentThread().isInterrupted()) { return; } // Your actual task logic here executeYourBusinessLogic(); } catch (InterruptedException e) { // Reset interrupt status so the thread can exit properly Thread.currentThread().interrupt(); } }, 0, 10, TimeUnit.SECONDS); }
2. Clean Up the Executor in destroy()
This is the most critical part—you need to gracefully shut down the executor and wait for tasks to finish (or force terminate if needed):
@Override public void destroy() { if (scheduler != null && !scheduler.isShutdown()) { // Stop accepting new tasks scheduler.shutdown(); try { // Wait up to 30 seconds for existing tasks to complete if (!scheduler.awaitTermination(30, TimeUnit.SECONDS)) { // If tasks are still running after 30s, force terminate them scheduler.shutdownNow(); // Wait an extra 5 seconds for the forced shutdown to complete scheduler.awaitTermination(5, TimeUnit.SECONDS); } } catch (InterruptedException e) { // If the wait is interrupted, force terminate immediately scheduler.shutdownNow(); // Reset interrupt status for the current thread Thread.currentThread().interrupt(); } } super.destroy(); }
Key Things to Remember
- Tasks Must Handle Interrupts: If your task includes blocking operations (like
Thread.sleep(), database calls, or IO), always catchInterruptedExceptionand either exit the task or reset the interrupt status. Without this,shutdownNow()won't be able to stop the thread. - Avoid Daemon Threads as a Quick Fix: While marking threads as daemon will let Tomcat kill them on shutdown, this can leave tasks in a half-completed state (leading to data inconsistencies or resource leaks). Manual shutdown is always safer.
- Debug with Thread Names: Naming your threads makes it easy to identify which thread is causing the timeout in Tomcat's shutdown logs—look for threads that are still running after the shutdown signal is sent.
内容的提问来源于stack exchange,提问作者flixe

