Exemplo útil de um gancho de desligamento em Java?


125

Estou tentando garantir que meu aplicativo Java tome medidas razoáveis ​​para ser robusto, e parte disso envolve o desligamento normal. Estou lendo sobre ganchos de desligamento e na verdade não entendo como utilizá-los na prática.

Existe um exemplo prático por aí?

Digamos que eu tenha um aplicativo realmente simples como este abaixo, que grave números em um arquivo, 10 em uma linha, em lotes de 100, e quero garantir que um determinado lote termine se o programa for interrompido. Entendo como registrar um gancho de desligamento, mas não faço ideia de como integrá-lo ao meu aplicativo. Alguma sugestão?

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

Respostas:


160

Você pode fazer o seguinte:

  • Deixe o gancho de desligamento definir algum AtomicBoolean (ou booleano volátil) "keepRunning" como false
  • (Opcionalmente, .interruptos threads de trabalho, se aguardarem dados em alguma chamada de bloqueio)
  • Aguarde writeBatcha conclusão dos threads de trabalho (executando no seu caso) chamando o Thread.join()método nos threads de trabalho.
  • Encerrar o programa

Algum código incompleto:

  • Adicione um static volatile boolean keepRunning = true;
  • Em run () você muda para

    for (int i = 0; i < N && keepRunning; ++i)
        writeBatch(pw, i);
  • Em main () você adiciona:

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

É mais ou menos assim que eu faço um gracioso "rejeitar todos os clientes ao pressionar Control-C" no terminal.


Dos documentos :

Quando a máquina virtual inicia sua sequência de desligamento, inicia todos os ganchos de desligamento registrados em uma ordem não especificada e os deixa executar simultaneamente. Quando todos os ganchos terminarem, ele executará todos os finalizadores não solicitados se a finalização na saída estiver ativada. Finalmente, a máquina virtual será interrompida.

Ou seja, um gancho de encerramento mantém a JVM em execução até que o gancho seja finalizado (retornado do método run ()) .


14
Ah - então, se eu entendi direito, a essência do que você está dizendo é que o gancho de desligamento tem a oportunidade de se comunicar com outros threads em execução no momento e adiar o desligamento da VM (por exemplo, usando Thread.join () ) até concluir que uma limpeza rápida foi concluída. Esse é o ponto que eu estava perdendo. obrigado!
Jason S

2
Sim. Você entendeu. Atualizou a resposta com algumas frases dos documentos.
Aioobe 27/05

1
Desculpe antecipadamente, mas o GracefulShutdownTest1 não deve implementar o Runnable?
Victor

Isso também funcionará? Uma GUI foi iniciada no thread principal? Eu sei que não é a melhor maneira de fazer isso (use o EDT), mas estou trabalhando no código antigo.
Agostino

1
@ MartynasJusevičius, a JVM termina quando todos os encadeamentos que não são daemon terminam. Se o mainThreadé um encadeamento daemon, então joingarantimos que aguardamos o término antes de deixar a JVM encerrar.
aioobe

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.