Перехватчики завершения работы в Java — это просто потоки, которые будут выполняться до того, как нормальное выполнение программы завершится. Давайте посмотрим, как и когда их использовать.

Как создать свой собственный хук отключения?

Вы можете легко создать свой собственный хук завершения работы, расширив класс Thread, так как ваш пользовательский хук также будет потоком. Затем вам нужно реализовать то, что вам нужно сделать внутри переопределенного метода запуска. В приведенном ниже фрагменте кода созданный мной класс CustomShutdownHook проверит, открыто ли сетевое соединение, и закроет его, если это так.

public class CustomShutdownHook extends Thread {
    private NetworkService networkService;
    
    public CustomShutdownHook(NetworkService networkService) {
        this.networkService = networkService;    
    }
    
    @Override
    public void run() {
        if (networkService.isConnected()) {
            networkService.disconnect();
        }
    }
}

Как зарегистрировать свой собственный хук выключения?

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

Вот простой фрагмент кода, который покажет вам, как это сделать.

Runtime.getRuntime().addShutdownHook(customShutdownHook);

Когда вызывается ваш настраиваемый хук отключения?

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

  1. Выход после завершения нормального хода программы. Это означает, что каждый поток, не являющийся демоном, завершает свое выполнение.
public static void main(String[] args) {
    // ...
    // ...
    // Here our program is about to end and our hook will run soon.
}

Другой пример, связанный с потоками демона

public class CustomShutdownHook extends Thread {
    private String text;

    public CustomShutdownHook(String a) {
        this.text = a;
    }

    @Override
    public void run() {
        System.out.println(text);
    }
}
// Example 1, infinite loop
public static void main(String[] args) {
    Thread nondaemonThread = new Thread(() -> {
        while (true);
    });
    nondaemonThread.start();
    Runtime.getRuntime().addShutdownHook(new CustomShutdownHook("someText"));
}
// Example 2, prints "someText"
public static void main(String[] args) {
    Thread daemonThread = new Thread(() -> {
        while (true);
    });
    daemonThread.setDaemon(true); // notice the difference here
    daemonThread.start();
    Runtime.getRuntime().addShutdownHook(new   CustomShutdownHook("someText"));
}

В примере 1 поток не является демоном, и наш хук не будет запущен, поскольку поток выполняет бесконечный цикл. В примере 2 мы установили поток как демон, и даже несмотря на то, что он выполняет бесконечный цикл, наш хук сработает и напечатает заданный текст.

2. Вызывается System.exit().

public static void main(String[] args) {
    // ...
    // ...
    System.exit(0);
    /* This call will cause our program to stop further execution     
       and trigger shutdown hook. */
}

3. Выход, управляемый пользователем, с помощью Ctrl + C

Рассмотрите возможность запуска вашей программы в консоли, и вы сигнализируете ей с помощью Ctrl + C, чтобы завершить ее. Это вызовет перехватчики выключения, которые вы зарегистрировали.

Существует также еще одна существенная проблема: когда у вас есть несколько регистров перехватчиков выключения, они не будут выполняться в том порядке, в котором вы их зарегистрировали.

Когда вы должны использовать перехватчики отключения?

Есть случаи, когда вы можете создавать и регистрировать пользовательские хуки. Если у вас есть определенные задачи резервного копирования, которые необходимо выполнить перед выходом, такие как закрытие сетевых подключений, вы можете воспользоваться этим. Но здесь я должен сказать, что вам лучше выполнять такие задачи очистки в соответствии с логикой вашей программы, например, когда нажата кнопка выхода в вашем пользовательском интерфейсе или определенная пользовательская команда, представленная в консоли, вы обрабатываете такие задачи очистки и не оставляете их необработанными. полагаясь на перехватчики выключения. Например, представьте ситуацию, когда ваши перехватчики завершения работы не выполняются из-за того, что используется kill -9singal или ОС неожиданно завершает работу, ваш процесс очистки внутри настраиваемых перехватчиков не будет выполняться, а соединения могут оставаться открытыми после того, как программа завершит работу. Чтобы предотвратить это, вы обрабатываете всю свою логику в обычном потоке и позволяете своим хукам гарантировать то, чего вы хотите достичь перед выходом. (Проверьте первый фрагмент)

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