Concepto clave
Para garantizar que Tomcat espere a que las tareas en el pool de hilos finalicen antes de detenerse, se debe seguir este enfoque:
- Crear un pool de hilos singleton global al iniciar la aplicación.
- Implementar un
ServletContextListenerpara gestionar el ciclo de vida. - Durante el apagado, invocar métodos de cierre gradual del pool:
shutdown()para rechazar nuevas tareas.awaitTermination(...)para bloquear hasta que las tareas en curso terminen.
- Forzar el cierre con
shutdownNow()si se excede el tiempo de espera.
Implementación completa del código
1. Singleton del pool de hilos (recomendado como clase de utilidad)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class GestorDeTareas {
// Pool de hilos global único
private static final ExecutorService SERVICIO_EJECUTOR = new ThreadPoolExecutor(
5,
10,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
public static ExecutorService obtenerServicio() {
return SERVICIO_EJECUTOR;
}
}
2. Implemetnación del oyente (elemento crítico)
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
@WebListener // Permite que Tomcat lo detecte automáticamente
public class ListenerApagadoPool implements ServletContextListener {
@Override
public void contextoIniciado(ServletContextEvent sce) {
// Inicialización al arrancar la aplicación (opcional)
System.out.println("Aplicación Tomcat iniciada");
}
@Override
public void contextoDestruido(ServletContextEvent sce) {
System.out.println("Tomcat deteniéndose, iniciando cierre del pool de hilos...");
ExecutorService ejecutor = GestorDeTareas.obtenerServicio();
// 1. Rechazar nuevas tareas
ejecutor.shutdown();
try {
// 2. Esperar a que las tareas existentes finalicen (ejemplo: 30 segundos)
if (!ejecutor.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println("El pool de hilos no se cerró a tiempo, forzando cierre");
// 3. Forzar cierre por timeout
ejecutor.shutdownNow();
}
} catch (InterruptedException e) {
System.err.println("Cierre del pool interrumpido");
ejecutor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("Pool de hilos cerrado de forma segura");
}
}
3. Alternativa sin anotaciones: configuración en web.xml
<listener>
<listener-class>com.miempresa.miproyecto.ListenerApagadoPool</listener-class>
</listener>
¿Por qué este método garantiza que Tomcat espere a las tareas?
- Durante el apagado, Tomcat invoca secuencialmente todos los métodos
contextoDestruidode losServletContextListener. - La llamada
awaitTermination(30, SECONDS)bloquea el hilo actual hasta que:- Todas las tareas finalican, o
- Se alcence el tiempo límite.
- Durante este bloqueo, Tomcat no continúa con la destrucción de filtros y servlets, logrando el efecto de espera deseado.
Puntos importantes a considerar
- No usar
Executors.newFixedThreadPool: su cola es ilimitada, lo que puede acumular demasiadas tareas y prolongar el apagado. - El tiempo en
awaitTerminationdebe ser razonable: típicamente entre 10 y 60 segundos, ajustado según la duración de tus tareas. - Las tareas deberían responder a interrupciones: de lo contrario,
shutdownNow()no podrá detenerlas, bloqueando el proceso hasta el timeout. - Evitar bucles infinitos en tareas asíncronas o programadas: esto impediría un cierre definitivo.