web-dev-qa-db-fra.com

Arrêter la minuterie programmée lors de l'arrêt de Tomcat

J'ai un fichier WAR déployé sur le serveur Tomcat, l'une des classes sera appelée au démarrage, puis la méthode init () programmera un minuteur pour se déclencher toutes les 5 heures pour effectuer certaines tâches.

Mon code init () ressemble à ceci:

public void init()
{
    TimerTask parserTimerTask = new TimerTask() {

        @Override
        public void run() {
            XmlParser.parsePage();
        }
    };

    Timer parserTimer = new Timer();
    parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}

Mon application fonctionne sans problème, mais lorsque j'arrête le Tomcat en utilisant /etc/init.d/Tomcat7 stop , alors je vérifie le journal (catalina.out ) il a une entrée comme celle-ci:

GRAVE: l'application Web [/ MyApplication] semble avoir démarré un thread nommé [Timer-0] mais ne l'a pas arrêtée. Ceci est très susceptible de créer une fuite de mémoire.

Je comprends que cela est dû au fait que je programme le minuteur, mais ma question est:

  1. Je n'ai pas défini setDeamon sur true, alors le minuteur ne devrait-il pas empêcher Tomcat de s'éteindre, plutôt que de le laisser fonctionner?
  2. Puis-je, dans mon application, détecter que Tomcat va être arrêté et annuler ma minuterie?
  3. Quelles sont les autres solutions que je peux utiliser pour résoudre ce problème?

Merci!

[~ # ~] mise à jour [~ # ~]

J'ai changé mon code pour le suivant en fonction d'une recherche et de la réponse de 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) {

}

Maintenant ma nouvelle question:

  1. J'annule d'abord TimerTask puis Timer, est-ce correct?
  2. Y a-t-il autre chose que je devrais faire?

Merci!

[~ # ~] mise à jour [~ # ~]

Ça ne marche pas. J'ai mis une déclaration de journalisation dans la méthode contextDestroyed (), après avoir arrêté Tomcat, le fichier journal ne contient que les éléments suivants:

PowderGodAppWebService -> [7 février 2012 04:09:46 PM] INFO (PowderGodAppWebService.Java:45) :: DÉTECTER LE SERVEUR Tomcat IS VA ARRÊTER PowderGodAppWebService -> [7 février 2012 04: 09:46 PM] INFO (PowderGodAppWebService.Java:46) :: ANNULER TÂCHE ET MINUTERIE

ANNULATION TERMINEE n'est pas là.

J'ai également vérifié les processus en cours d'exécution (je ne suis pas un expert Linux, j'utilise donc simplement le moniteur d'activité de Mac.

  • Assurez-vous qu'aucun processus Java n'est en cours d'exécution
  • Démarrez Tomcat, notez le PID de ce Java processus
  • Arrêtez Tomcat
  • Trouvé que le processus Tomcat est parti
  • Démarrez Tomcat, notez le PID de ce Java processus
  • Déployer mon fichier de guerre
  • Échantillon du processus, voir [Timer-0] thread is there
  • Arrêter Tomcat
  • Trouvé que le processus est toujours là
  • Échantillonner le processus
  • Voir [Timer-0] est toujours là

[~ # ~] fixe [~ # ~]

J'ai changé mon code en parserTimer = new Timer(true); pour que ma minuterie fonctionne comme un thread démon car la contextDestroyed() est appelée après l'arrêt de Tomcat.

"Tous les servlets et filtres auront été détruits avant que les ServletContextListeners ne soient informés de la destruction du contexte."

http://docs.Oracle.com/javaee/6/api/javax/servlet/ServletContextListener.html

22
Derek Li

Ne pas utiliser Timer dans un environnement Java EE! Si la tâche lève une exception d'exécution, puis tout Timer est tué et ne fonctionnera plus. Vous devez essentiellement redémarrer tout le serveur pour le remettre en marche. De plus, il est sensible aux changements d'horloge système.

Utilisez plutôt ScheduledExecutorService . Il n'est pas sensible aux exceptions lancées dans les tâches ni aux changements d'horloge système. Vous pouvez l'arrêter par sa méthode shutdownNow().

Voici un exemple de ce à quoi l'ensemble de l'implémentation ServletContextListener peut ressembler (remarque: pas d'enregistrement dans web.xml requis grâce au nouveau @WebListener annotation):

@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();
    }

}
65
BalusC

La méthode de destruction des servlets est appelée alors que la servlet est sur le point d'être déchargée. Vous pouvez annuler le minuteur à partir de là, à condition que vous modifiiez la portée du parserTimer lui-même pour en faire une variable d'instance. Je ne vois pas de problème avec cela à condition que vous y accédiez uniquement depuis init et destroy.

Le moteur de servlet est libre de décharger le servlet chaque fois qu'il le juge opportun, mais en pratique, je ne l'ai jamais vu appelé lorsque le moteur de servlet est arrêté - d'autres personnes peuvent avoir d'autres expériences de cela, bien que cela puisse me prouver le contraire.

1
DaveH