Ejemplo útil de un gancho de apagado en Java?

126

Estoy tratando de asegurarme de que mi aplicación Java tome medidas razonables para ser robusta, y parte de eso implica cerrar con gracia. Estoy leyendo sobre ganchos de apagado y en realidad no entiendo cómo usarlos en la práctica.

¿Hay algún ejemplo práctico por ahí?

Digamos que tenía una aplicación realmente simple como esta a continuación, que escribe números en un archivo, 10 en una línea, en lotes de 100, y quiero asegurarme de que un lote determinado finalice si el programa se interrumpe. Entiendo cómo registrar un gancho de apagado, pero no tengo idea de cómo integrarlo en mi aplicación. ¿Alguna sugerencia?

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();
        }
    }
}
Jason S
fuente
Consulte también stackoverflow.com/questions/8722826/…
flow2k el

Respuestas:

160

Podrías hacer lo siguiente:

  • Deje que el gancho de cierre establezca algo de "keepRunning" AtomicBoolean (o boolean volátil) en falso
  • (Opcionalmente, .interruptlos hilos de trabajo si esperan datos en alguna llamada de bloqueo)
  • Espere writeBatcha que terminen los hilos de trabajo (ejecutándose en su caso), llamando al Thread.join()método en los hilos de trabajo.
  • Terminar el programa

Algún código incompleto:

  • Agrega un static volatile boolean keepRunning = true;
  • En run () cambias a

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

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

Así es más o menos como hago un elegante "rechazar a todos los clientes al presionar Control-C" en la terminal.


De los documentos :

Cuando la máquina virtual comienza su secuencia de apagado, iniciará todos los ganchos de apagado registrados en un orden no especificado y los dejará correr simultáneamente. Cuando todos los ganchos hayan terminado, ejecutará todos los finalizadores no invocados si se ha habilitado la finalización al salir. Finalmente, la máquina virtual se detendrá.

Es decir, un gancho de apagado mantiene la JVM funcionando hasta que el gancho ha terminado (devuelto por el método run () .

aioobe
fuente
14
Ah, así que si entiendo bien, lo esencial de lo que está diciendo es que el gancho de apagado tiene la oportunidad de comunicarse con otros subprocesos que se ejecutan actualmente y de detener el apagado de VM (por ejemplo, usando Thread.join () ) hasta que esté seguro de que se ha completado una limpieza rápida. Ese es el punto que me faltaba. ¡Gracias!
Jason S
2
Si. Lo tienes. Se actualizó la respuesta con algunas oraciones de los documentos.
aioobe
1
Lo siento de antemano, pero ¿no debería GracefulShutdownTest1 implementar Runnable?
Victor
¿Esto también funcionará si se inició una GUI en el hilo principal? Sé que no es la mejor manera de hacerlo (use EDT en su lugar), pero estoy trabajando en un código antiguo.
Agostino
1
@ MartynasJusevičius, la JVM finaliza cuando todos los subprocesos no daemon han finalizado. Si el mainThreades un hilo de demonio, entonces las joingarantías de que esperamos a que termine antes de dejar que la JVM se apague.
aioobe