Полезный пример выключения в Java?

Я пытаюсь удостовериться, что мое приложение Java принимает разумные меры, чтобы быть надежным, а часть этого требует прекрасного изложения. Я читаю о shutdown hooks, и я фактически не понимаю, как их использовать на практике.

Есть ли там практический пример?

Скажем, у меня было действительно простое приложение, подобное этому ниже, которое записывает числа в файл, 10 в строку, партиями по 100, и я хочу убедиться, что данная партия заканчивается, если программа прерывается. Я получаю, как зарегистрировать крюк отключения, но я понятия не имею, как интегрировать это в мое приложение. Любые предложения?

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

Ответ 1

Вы можете сделать следующее:

  • Пусть крюк отключения установит некоторый AtomicBoolean (или volatile boolean) "keepRunning" на false
  • (необязательно, .interrupt рабочие потоки, если они ждут данных в некотором блокирующем вызове)
  • Подождите, пока рабочие потоки (выполняющие writeBatch в вашем случае) завершатся, вызывая метод Thread.join() для рабочих потоков.
  • Завершить работу программы

Некоторые отрывочные коды:

  • Добавить static volatile boolean keepRunning = true;
  • В run() вы переключаетесь на

    for (int i = 0; i < N && keepRunning; ++i)
        writeBatch(pw, i);
    
  • В main() вы добавляете:

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

Это примерно так, как я делаю изящное "отклонение всех клиентов при попадании Control-C" в терминал.


От документы:

Когда виртуальная машина начнет свою последовательность выключения, она запустит все зарегистрированные крючки остановки в некотором неуказанном порядке и позволит им запускать одновременно. Когда все крючки закончены, он будет запускать все неинвизированные финализаторы, если финализация на выходе будет включена. Наконец, виртуальная машина остановится.

То есть, выключатель остановки держит JVM до тех пор, пока крюк не завершится (возвращается из метода run() -

Ответ 2

Завершение работы Крюки - это нестационарные потоки, зарегистрированные в Runtime.addShutdownHook(). JVM не дает никаких гарантий относительно порядка, в котором запускаются крючки остановки. Для получения дополнительной информации см. http://techno-terminal.blogspot.in/2015/08/shutdown-hooks.html