Изящное завершение потоков и исполнителей

Следующий фрагмент кода пытается совместить это.

Кодирует цикл навсегда и проверяет, есть ли какие-либо ожидающие обработки запросы. Если он есть, он создает новый поток для обработки запроса и отправляет его исполнителю. Как только все потоки выполнены, он спит в течение 60 секунд и снова проверяет ожидающие запросы.

public static void main(String a[]){
    //variables init code omitted
    ExecutorService service = Executors.newFixedThreadPool(15);
    ExecutorCompletionService<Long> comp = new ExecutorCompletionService<Long>(service);
    while(true){
        List<AppRequest> pending = service.findPendingRequests();
        int noPending = pending.size();
        if (noPending > 0) {
            for (AppRequest req : pending) {
                Callable<Long> worker = new RequestThread(something, req);
                comp.submit(worker);
            }
        }
        for (int i = 0; i < noPending; i++) {
            try {
                Future<Long> f = comp.take();
                long name;
                try {
                    name = f.get();
                    LOGGER.debug(name + " got completed");
                } catch (ExecutionException e) {
                    LOGGER.error(e.toString());
                }
            } catch (InterruptedException e) {
                LOGGER.error(e.toString());
            }
        }
        TimeUnit.SECONDS.sleep(60);
    }

  }

Мой вопрос - большая часть обработки этих потоков связана с базой данных. И эта программа будет работать на машине Windows. Что происходит с этими потоками, когда кто-то пытается отключить или выйти из машины? Как изящно завершить работающие потоки, а также исполнитель.

Ответ 1

Типичное упорядоченное закрытие ExecutorService может выглядеть примерно так:

final ExecutorService executor;

Runtime.getRuntime().addShutdownHook(new Thread() {
    public void run() {
        executor.shutdown();
        if (!executor.awaitTermination(SHUTDOWN_TIME)) { //optional *
            Logger.log("Executor did not terminate in the specified time."); //optional *
            List<Runnable> droppedTasks = executor.shutdownNow(); //optional **
            Logger.log("Executor was abruptly shut down. " + droppedTasks.size() + " tasks will not be executed."); //optional **
        }
    }
});

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

Обратите внимание, что решение выше будет работать, когда пользователь выдает прерывание в ваш java процесс или когда ваш ExecutorService содержит только потоки демона. Если вместо этого ExecutorService содержит потоки не-демона, которые еще не завершены, JVM не будет пытаться завершить работу, и поэтому крючки отключения не будут вызываться.

Если вы пытаетесь завершить процесс как часть жизненного цикла дискретного приложения (а не службы), тогда код завершения не должен быть помещен внутри крюка выключения, но в соответствующем месте, где программа предназначена для завершения.

Ответ 2

В книге " Java Concurrency на практике говорится:

7.4. Выключение JVM

JVM может закрываться либо в упорядоченным или резким образом. Упорядоченный выключение начинается, когда последний "нормальный" (nondaemon) поток завершается, кто-то вызывает System.exit, или другими средствами, специфичными для платформы (например, отправка SIGINT или удар Ctrl-C). [...]

7.4.1. Замыкающие крюки

При упорядоченном завершении работы JVM сначала запускает все зарегистрированные крючки остановки. Крюки остановки - это неартированные потоки которые зарегистрированы Runtime.addShutdownHook. JVM делает нет гарантий относительно порядка, в котором запускаются крючки остановки. Если потоки приложений (демон или nondaemon) все еще работают на время выключения, они продолжают работать одновременно с выключением обработать. Когда все завершающие крючки имеют завершено, JVM может выбрать запуск финализаторы, если runFinalizersOnExit true, а затем останавливается. JVM не делает попытка остановить или прервать любые потоки приложений работает при выключенном времени; они есть внезапно прекращается, когда JVM в конечном итоге останавливается. Если выключение крючки или финализаторы не завершаются, затем упорядоченный процесс выключения "зависает", и JVM должен быть выключен резко. [...]

Важными битами являются: "JVM не пытается остановить или прервать любые потоки приложений, которые все еще работают во время отключения, они внезапно прекращаются, когда JVM в конечном итоге останавливается". поэтому я предполагаю, что соединение с БД внезапно прекратится, если нет завершающих крючков, чтобы сделать изящную очистку (если вы используете фреймворки, они обычно предоставляют такие прерывания остановки). По моему опыту, сеанс работы с БД может оставаться до тех пор, пока он не будет отключен БД и т.д., Когда приложение. заканчивается без таких крючков.

Ответ 4

Вы можете вызвать shutdown() на ExecutorService:

Инициирует упорядоченное выключение, в котором ранее поставленные задачи но никаких новых задач не будет принимаются.

или вы можете вызвать shutdownNow():

Попытки остановить все активно выполнение задач, прекращение обработки ожидающих задач и возвращает список задач, которые ожидали выполнение.

Никаких гарантий не существует стремления к максимальному усилению обработка, активно выполняющая задачи. Например, типичные реализации будет отменено через Thread.interrupt(), поэтому любая задача, которая не отвечает на прерывания никогда не прекращаются.

Какой из них вы вызываете, зависит от того, насколько сильно вы хотите его остановить.