Exemple utile de hook d'arrêt en Java?


126

J'essaie de m'assurer que mon application Java prend des mesures raisonnables pour être robuste, et cela implique en partie l'arrêt en douceur. Je lis sur hooks d'arrêt et je ne sais pas vraiment comment les utiliser dans la pratique.

Existe-t-il un exemple pratique?

Disons que j'ai eu une application très simple comme celle-ci ci-dessous, qui écrit des nombres dans un fichier, 10 sur une ligne, par lots de 100, et je veux m'assurer qu'un lot donné se termine si le programme est interrompu. Je comprends comment enregistrer un hook d'arrêt mais je n'ai aucune idée de comment l'intégrer dans mon application. Aucune suggestion?

package com.example.test.concurrency;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class GracefulShutdownTest1 {
    final private int N;
    final private File f;
    public GracefulShutdownTest1(File f, int N) { this.f=f; this.N = N; }

    public void run()
    {
        PrintWriter pw = null;
        try {
            FileOutputStream fos = new FileOutputStream(this.f);
            pw = new PrintWriter(fos);
            for (int i = 0; i < N; ++i)
                writeBatch(pw, i);
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally
        {
            pw.close();
        }       
    }

    private void writeBatch(PrintWriter pw, int i) {
        for (int j = 0; j < 100; ++j)
        {
            int k = i*100+j;
            pw.write(Integer.toString(k));
            if ((j+1)%10 == 0)
                pw.write('\n');
            else
                pw.write(' ');
        }
    }

    static public void main(String[] args)
    {
        if (args.length < 2)
        {
            System.out.println("args = [file] [N] "
                    +"where file = output filename, N=batch count");
        }
        else
        {
            new GracefulShutdownTest1(
                    new File(args[0]), 
                    Integer.parseInt(args[1])
            ).run();
        }
    }
}

Réponses:


160

Vous pouvez faire ce qui suit:

  • Laissez le hook d'arrêt définir certains AtomicBoolean (ou boolean volatile) "keepRunning" sur false
  • (En option, .interrupt les threads de travail s'ils attendent des données dans un appel de blocage)
  • Attendez que les threads de travail (s'exécutant writeBatchdans votre cas) se terminent, en appelant leThread.join() méthode sur les threads de travail.
  • Terminer le programme

Un peu de code sommaire:

  • Ajouter un static volatile boolean keepRunning = true;
  • Dans run () vous changez en

    for (int i = 0; i < N && keepRunning; ++i)
        writeBatch(pw, i);
  • Dans main () vous ajoutez:

    final Thread mainThread = Thread.currentThread();
    Runtime.getRuntime().addShutdownHook(new Thread() {
        public void run() {
            keepRunning = false;
            mainThread.join();
        }
    });

C'est à peu près comment je fais un gracieux "rejeter tous les clients en frappant Control-C" dans le terminal.


À partir de la documentation :

Lorsque la machine virtuelle commence sa séquence d'arrêt, elle démarre tous les hooks d'arrêt enregistrés dans un ordre non spécifié et les laisse s'exécuter simultanément. Une fois tous les hooks terminés, il exécutera tous les finaliseurs non sollicités si la finalisation à la sortie a été activée. Enfin, la machine virtuelle s'arrêtera.

Autrement dit, un hook d'arrêt maintient la JVM en cours d'exécution jusqu'à ce que le hook soit terminé (renvoyé par la méthode run () .


14
Ah - donc si je comprends bien, l'essentiel de ce que vous dites est que le hook d'arrêt a la possibilité de communiquer avec d'autres threads en cours d'exécution et de retarder l'arrêt de la VM (par exemple en utilisant Thread.join () ) jusqu'à ce qu'il soit convaincu qu'un nettoyage rapide a été effectué. C'est le point qui me manquait. Merci!
Jason S

2
Oui. Tu l'as eu. Mise à jour de la réponse avec quelques phrases de la documentation.
aioobe

1
Désolé à l'avance, mais GracefulShutdownTest1 ne devrait-il pas implémenter Runnable?
Victor

Cela fonctionnera-t-il également une interface graphique a été démarrée dans le fil principal? Je sais que ce n'est pas la meilleure façon de le faire (utilisez plutôt EDT), mais je travaille sur un vieux code.
Agostino

1
@ MartynasJusevičius, la JVM se termine lorsque tous les threads non démon se sont terminés. Si le mainThreadest un thread démon, alors les joingaranties que nous attendons qu'il se termine avant de laisser la JVM s'arrêter.
aioobe

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.