Esempio utile di hook di spegnimento in Java?


126

Sto cercando di assicurarmi che la mia applicazione Java adotti misure ragionevoli per essere robusta, e parte di ciò comporta lo spegnimento con grazia. Sto leggendo degli hook di arresto e in realtà non capisco come usarli in pratica.

C'è un esempio pratico là fuori?

Diciamo che avevo un'applicazione molto semplice come questa qui sotto, che scrive i numeri su un file, 10 su una riga, in batch di 100, e voglio assicurarmi che un determinato batch finisca se il programma viene interrotto. Ottengo come registrare un hook di spegnimento ma non ho idea di come integrarlo nella mia applicazione. Eventuali suggerimenti?

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

Risposte:


160

Puoi fare quanto segue:

  • Lascia che l'hook di spegnimento imposti AtomicBoolean (o volatile booleano) "keepRunning" su false
  • (Facoltativamente, .interrupti thread di lavoro se attendono i dati in una chiamata di blocco)
  • Attendi il completamento dei thread di lavoro (eseguendo writeBatchnel tuo caso), chiamando il Thread.join()metodo sui thread di lavoro.
  • Termina il programma

Qualche codice impreciso:

  • Aggiungere un static volatile boolean keepRunning = true;
  • In run () si passa a

    for (int i = 0; i < N && keepRunning; ++i)
        writeBatch(pw, i);
    
  • In main () aggiungi:

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

È più o meno così che faccio un grazioso "rifiuto di tutti i client colpendo Control-C" nel terminale.


Dai documenti :

Quando la macchina virtuale inizia la sua sequenza di arresto, avvia tutti gli hook di arresto registrati in un ordine non specificato e li lascia funzionare contemporaneamente. Al termine di tutti gli hook, verranno eseguiti tutti i finalizzatori non invocati se la finalizzazione all'uscita è stata abilitata. Alla fine, la macchina virtuale si arresterà.

Cioè, un hook di shutdown mantiene la JVM in esecuzione fino a quando l'hook non termina (restituito dal metodo run () .


14
Ah - quindi, se ho capito bene, l'essenza di ciò che stai dicendo è che l'hook dell'arresto ha l'opportunità di comunicare con altri thread attualmente in esecuzione e di bloccare l'arresto della VM (ad esempio utilizzando Thread.join () ) fino a quando non viene verificato che è stata completata una pulizia rapida. Questo è il punto che mi mancava. Grazie!
Jason S,

2
Sì. Avete capito bene. Aggiornata la risposta con alcune frasi dai documenti.
aioobe,

1
Scusa in anticipo, ma GracefulShutdownTest1 non dovrebbe implementare Runnable?
Victor,

Funzionerà anche una GUI nel thread principale? So che non è il modo migliore per farlo (usa invece EDT), ma sto lavorando su un vecchio codice.
Agostino,

1
@ MartynasJusevičius, la JVM termina quando tutti i thread non daemon sono terminati. Se si mainThreadtratta di un thread daemon, quindi le joingaranzie che aspettiamo che finisca prima di chiudere la JVM.
aioobe,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.