Остановить запланированный таймер при выключении tomcat
У меня есть файл WAR, развернутый на сервере Tomcat, один из классов будет вызван во время запуска, а затем метод init() будет планировать запуск таймера каждые 5 часов для выполнения некоторых задач.
Мой код init() выглядит следующим образом:
public void init()
{
TimerTask parserTimerTask = new TimerTask() {
@Override
public void run() {
XmlParser.parsePage();
}
};
Timer parserTimer = new Timer();
parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}
Мое приложение работает без проблем, но когда я завершаю Tomcat с помощью /etc/init.d/tomcat7 stop, тогда я проверяю журнал (catalina.out), у него есть запись вроде этого:
SEVERE: веб-приложение [/MyApplication], похоже, запустило поток с именем [Timer-0], но не остановило его. Вероятно, это приведет к утечке памяти.
Я понимаю, что это вызвано тем, что я расписал таймер, но мой вопрос:
- Я не установил
setDeamon
в значение true, поэтому не следует ли отключать таймер Tomcat, а не работать?
- Могу ли я в своем приложении обнаружить, что Tomcat будет отключен и отменит мой таймер?
- Каковы другие решения, которые я могу использовать для решения этой проблемы?
Спасибо!
UPDATE
Я изменил свой код на следующий, основанный на некотором поиске и ответе DaveHowes.
Timer parserTimer;
TimerTask parserTimerTask;
public void init()
{
parserTimerTask = new TimerTask() {
@Override
public void run() {
XmlParser.parsePage();
}
};
parserTimer = new Timer();
parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
Logger logger = Logger.getRootLogger();
logger.info("DETECT TOMCAT SERVER IS GOING TO SHUT DOWN");
logger.info("CANCEL TIMER TASK AND TIMER");
otsParserTimerTask.cancel();
otsParserTimer.cancel();
logger.info("CANCELING COMPLETE");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
}
Теперь мой новый вопрос:
- Я отменяю TimerTask сначала, а затем Timer, это правильно?
- Есть ли еще что я должен делать?
Спасибо!
UPDATE
Это не работает. Я положил некоторый оператор регистрации в метод contextDestroyed(), после того, как я завершаю Tomcat, файл журнала имеет только следующее:
PowderGodAppWebService → [07 Фев 2012 04:09:46 PM] INFO (PowderGodAppWebService.java:45):: DETECT TOMCAT SERVER НАХОДИТСЯ В ОТНОШЕНИИ
PowderGodAppWebService → [07.02.2012 04:09:46] INFO (PowderGodAppWebService.java:46):: ОТМЕНА ТАЙМЕРА ТАЙНА И ТАЙМЕР
ОТМЕНА ПОЛНОГО не существует.
Я также проверил процессы, которые выполняются (я не эксперт по Linux, поэтому просто использую монитор активности Mac.
- Убедитесь, что java-процесс не запущен.
- Запустите Tomcat, обратите внимание на PID этого java-процесса.
- Остановить Tomcat
- Найден процесс Tomcat не работает
- Запустите Tomcat, обратите внимание на PID этого java-процесса.
- Разверните мой файл войны
- Пример процесса, см. [Timer-0] thread есть
- Shutdown Tomcat
- Установлено, что процесс все еще существует
- Пример процесса
- См. [Timer-0] все еще существует
Fixed
Я изменил свой код на parserTimer = new Timer(true);
, чтобы мой таймер работал как поток демона, потому что вызов contextDestroyed()
вызывается после того, как Tomcat фактически завершает работу.
"Все сервлеты и фильтры будут уничтожены до того, как будут обнаружены сведения об уничтожении контекста ServletContextListeners.
http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContextListener.html
Ответы
Ответ 1
Do не использовать Timer
в среде Java EE! Если задача выдает исключение во время выполнения, то весь Timer
будет убит и больше не будет работать. Вам необходимо перезагрузить весь сервер, чтобы запустить его снова. Кроме того, он чувствителен к изменениям в системных часах.
Используйте ScheduledExecutorService
. Он не чувствителен к исключениям, брошенным в задачи или к изменениям в системных часах. Вы можете отключить его по методу shutdownNow()
.
Вот пример того, как может выглядеть вся реализация ServletContextListener
(обратите внимание: не требуется регистрация в web.xml
, связанная с новой аннотацией @WebListener
):
@WebListener
public class BackgroundJobManager implements ServletContextListener {
private ScheduledExecutorService scheduler;
@Override
public void contextInitialized(ServletContextEvent event) {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new YourParsingJob(), 0, 5, TimeUnit.HOUR);
}
@Override
public void contextDestroyed(ServletContextEvent event) {
scheduler.shutdownNow();
}
}
Ответ 2
Метод уничтожения сервлетов вызывается, когда сервлет вот-вот будет выгружен. Вы можете отменить таймер изнутри, указав, что вы изменили область действия самого parserTimer, чтобы сделать его переменной экземпляра. Я не вижу проблемы с этим при условии, что вы обращаетесь к нему только изнутри init и destroy.
Механизм сервлетов может разгружать сервлет всякий раз, когда он сочтет нужным, но в пратисе я только когда-либо видел, что он вызван, когда движок сервлета остановлен - у других людей может быть другой опыт, хотя это может оказаться мне неправильным.
Ответ 3
Попробуйте использовать фреймворк для планирования.
Если вы адаптируете Spring Framework, вы можете использовать возможности сборки в планировании.
При планировании с помощью Spring у меня никогда не было проблем с остановкой сервера приложений.
Ответ 4
Поместите parseTimer.purge() в свой onContetexyDestroyed.It удалит все таймеры в очереди, если они существуют.