Przydatny przykład haka zamykającego w Javie?

126

Próbuję się upewnić, że moja aplikacja Java podejmuje rozsądne kroki, aby być niezawodną, ​​a część z nich obejmuje bezpieczne zamykanie. Czytam o hakach zamykających i tak naprawdę nie wiem, jak ich używać w praktyce.

Czy istnieje praktyczny przykład?

Załóżmy, że miałem bardzo prostą aplikację, taką jak ta poniżej, która zapisuje liczby do pliku, 10 do wiersza, w partiach po 100 i chcę się upewnić, że dana partia zakończy się, jeśli program zostanie przerwany. Rozumiem, jak zarejestrować podpięcie zamykające, ale nie mam pojęcia, jak zintegrować to z moją aplikacją. Jakieś sugestie?

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.
źródło

Odpowiedzi:

160

Możesz wykonać następujące czynności:

  • Pozwól hakowi zamykającemu ustawić wartość AtomicBoolean (lub volatile boolean) „keepRunning” na false
  • (Opcjonalnie .interruptwątki robocze, jeśli czekają na dane w wywołaniu blokującym)
  • Poczekaj na zakończenie wątków roboczych (wykonywanych writeBatchw twoim przypadku), wywołując Thread.join()metodę w wątkach roboczych.
  • Zakończ program

Mały kod:

  • Dodać static volatile boolean keepRunning = true;
  • W run () zmieniasz na

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

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

Tak mniej więcej wdzięcznie „odrzucam wszystkich klientów po naciśnięciu Control-C” w terminalu.


Z dokumentów :

Gdy maszyna wirtualna rozpocznie sekwencję zamykania, uruchomi wszystkie zarejestrowane punkty zaczepienia zamykania w jakiejś nieokreślonej kolejności i pozwoli im działać jednocześnie. Gdy wszystkie przechwyty zakończą się, uruchomione zostaną wszystkie nie wywołane finalizatory, jeśli włączono finalizację przy wyjściu. Wreszcie maszyna wirtualna zostanie zatrzymana.

Oznacza to, że podpięcie zamykające utrzymuje działanie maszyny JVM do momentu zakończenia przechwytywania (zwróconego z metody run () .

aioobe
źródło
14
Ach - więc jeśli dobrze rozumiem, sednem tego, co mówisz, jest to, że hak zamykający ma możliwość komunikowania się z innymi aktualnie uruchomionymi wątkami i wstrzymania zamykania maszyny wirtualnej (np. Za pomocą Thread.join () ), dopóki nie upewnisz się, że szybkie czyszczenie zostało zakończone. To jest punkt, którego mi brakowało. dzięki!
Jason S
2
Tak. Masz to. Zaktualizowałem odpowiedź kilkoma zdaniami z dokumentacji.
aioobe
1
Przepraszam z góry, ale czy GracefulShutdownTest1 nie powinien implementować Runnable?
Victor,
Czy to również zadziała, gdy GUI został uruchomiony w głównym wątku? Wiem, że to nie jest najlepszy sposób (zamiast tego użyj EDT), ale pracuję na starym kodzie.
Agostino
1
@ MartynasJusevičius, maszyna JVM kończy działanie po zakończeniu wszystkich wątków niebędących demonami. Jeśli mainThreadjest to wątek demona, to joingwarantuje, że czekamy na jego zakończenie, zanim pozwolimy na zamknięcie maszyny JVM.
aioobe