Интерфейс ExecutorService предоставляет 3 метода shutdown(), shutdownNow() и awaitTermination() для управления завершением задач, переданных исполнителям. Узнайте, как использовать эти методы при различных требованиях.
1. Разница между shutdown(), shutdownNow() и awaitTermination()
Давайте начнем с проверки синтаксиса этих методов.
void shutdown();List<Runnable> shutdownNow();boolean awaitTermination(long timeout, TimeUnit unit);
shutdown() инициирует упорядоченное завершение работы, при котором выполняются ранее отправленные задачи, но новые задачи не принимаются. Этот метод не ждет завершения выполнения ранее отправленных задач(но не начатых).
shutdownNow() принудительно пытается остановить все активно выполняющиеся задачи, останавливает обработку ожидающих задач и возвращает список задач, ожидающих выполнения. Этот метод не ждет завершения активно выполняющихся задач и пытается остановить их принудительно. Нет никаких гарантий, кроме наилучших попыток остановить обработку активно выполняющихся задач. Эта реализация отменяет задачи через Thread.interrupt(), поэтому любая задача, которая не отвечает на прерывания, может никогда не завершиться.
Функция awaitTermination(long timeout, TimeUnit unit) блокируется до тех пор, пока не завершится выполнение всех задач после запроса на завершение работы, или не истечет время ожидания, или не будет прерван текущий поток, в зависимости от того, что произойдет раньше.
Помните, что awaitTermination() вызывается после запроса shutdown().
2. Использование shutdown() и awaitTermination()
2.1 Когда использовать
В общем случае ExecutorService не будет автоматически уничтожен, если нет задачи для обработки. Он останется активным и будет ждать появления новых задач. Это просто означает, что JVM не завершится, если мы этого ожидаем.
Такое поведение полезно в случае веб-/настольных приложений, которые выполняют эти задачи в течение неограниченного времени в зависимости от поступления новых задач.
Но когда у нас есть приложение, которое не использует исполнитель очень часто, и мы хотим освободить память для использования другими компонентами, мы должны остановить исполнитель. Но мы не можем просто так остановить исполнитель. Мы должны быть уверены, что текущий набор задач, отправленных исполнителю, должен быть завершен и не будет убит в процессе.
Чтобы разрешить выполнение ранее отправленных задач, не разрешить выполнение новых задач и дождаться завершения отправленных задач, нам нужно использовать shutdown() с awaitTermination().
2.2 Пример
В следующем примере мы запланировали 3 задачи, которые будут выполнены с задержкой в 10, 20 и 30 секунд.
Через 15 секунд планирования задач мы отключаем исполнитель, но не блокируем выполнение программы, так как выполняются операторы печати. На данный момент завершена только одна задача, которая была выполнена после 10-секундной задержки.
Как только мы вызываем awaitTermination(), он блокирует выполнение и ждет, пока все задачи не будут выполнены. После выполнения всех задач выполнение программы возобновляется, и теперь мы можем получить доступ к результатам всех задач, выполненных службой-исполнителем.
public class ShutdownExecutor {public static void main(String[] args) throws InterruptedException {ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);System.out.println("WorkerTasks scheduled at : " + LocalDateTime.now());ScheduledFuture<String> result1 = executor.schedule(new WorkerTask("WorkerTask-1"), 10, TimeUnit.SECONDS);ScheduledFuture<String> result2 = executor.schedule(new WorkerTask("WorkerTask-2"), 20, TimeUnit.SECONDS);ScheduledFuture<String> result3 = executor.schedule(new WorkerTask("WorkerTask-3"), 30, TimeUnit.SECONDS);Thread.sleep(15_000);System.out.println("***********Shutting down the executor service*********");executor.shutdown();System.out.println("***********Tasks are partially completed*********");System.out.println("Task-1 is done : " + result1.isDone());System.out.println("Task-2 is done : " + result2.isDone());System.out.println("Task-3 is done : " + result3.isDone());System.out.println("***********Waiting for tasks to be complete*********");executor.awaitTermination(1, TimeUnit.MINUTES);System.out.println("***********All tasks are completed now*********");System.out.println("Task-1 is done : " + result1.isDone());System.out.println("Task-2 is done : " + result2.isDone());System.out.println("Task-3 is done : " + result3.isDone());}}class WorkerTask implements Callable<String> {private final String name;public WorkerTask(String name) {this.name = name;}@Overridepublic String call() throws Exception {System.out.println("WorkerTask [" + name + "] executed on : "+ LocalDateTime.now().toString());return "WorkerTask [" + name + "] is SUCCESS !!";}}
Вывод программы.
WorkerTasks запланирован на: 2022-08-07T01:56:21.446915400WorkerTask [WorkerTask-1] выполнено: 2022-08-07T01:56:31.466320300***********Завершение работы службы исполнителя**********************Задачи выполнены частично**********Задача 1 выполнена: верноЗадача 2 выполнена: ложьЗадача 3 выполнена: ложь***********Ожидание выполнения задач***********WorkerTask [WorkerTask-2] выполнено: 2022-08-07T01:56:41.466846400WorkerTask [WorkerTask-3] выполнено: 2022-08-07T01:56:51.470540700***********Все задачи выполнены***********Задача 1 выполнена: верноЗадача 2 выполнена: верноЗадача 3 выполнена: верно
3. Использование shutdownNow()
shutdownNow() — это жесткий сигнал для немедленного уничтожения ExecutorService вместе с остановкой выполнения всех текущих и поставленных в очередь задач. Используйте этот метод, когда мы хотим, чтобы приложение немедленно прекратило обработку всех задач.
Если в предыдущем примере заменить executor.shutdown() на executor.shutdownNow(), то мы увидим, что как только вызывается shutdownNow(), выполнение программы нигде не блокируется, а выполнение задач немедленно останавливается. Здесь awaitTermination() не имеет никакого эффекта в этом случае.
Thread.sleep(15_000);System.out.println("***********Shutting down the executor service*********");executor.shutdownNow();
Вывод программы.
WorkerTasks запланирован на: 2022-08-07T01:57:25.427667900WorkerTask [WorkerTask-1] выполнено: 2022-08-07T01:57:35.436689400***********Завершение работы службы исполнителя**********************Задачи выполнены частично**********Задача 1 выполнена: верноЗадача 2 выполнена: ложьЗадача 3 выполнена: ложь***********Ожидание выполнения задач**********************Все задачи выполнены***********Задача 1 выполнена: верноЗадача 2 выполнена: ложьЗадача 3 выполнена: ложь
4. Передовая практика
Правильный способ завершения работы службы исполнителя, как предлагается в документации Java, выглядит следующим образом. Он завершает работу службы исполнителя и некоторое время ждет завершения отправленных задач.
Если запущенные задачи не завершаются в течение определенного времени, они принудительно завершаются.
void shutdownAndAwaitTermination(ExecutorService pool) {// Disable new tasks from being submittedpool.shutdown();try {// Wait a while for existing tasks to terminateif(!pool.awaitTermination(60, TimeUnit.SECONDS)) {// Cancel currently executing tasks forcefullypool.shutdownNow();// Wait a while for tasks to respond to being cancelledif(!pool.awaitTermination(60, TimeUnit.SECONDS))System.err.println("Pool did not terminate");}} catch(InterruptedException ex) {//(Re-)Cancel if current thread also interruptedpool.shutdownNow();// Preserve interrupt statusThread.currentThread().interrupt();}}
Чтобы использовать этот метод, просто передайте ссылку ExecutorService в этот метод.
ExecutorService executorService = Executors.newFixedThreadPool(1);executorService.submit(new WorkerTask("1"));executorService.submit(new WorkerTask("2"));executorService.submit(new WorkerTask("3"));shutdownAndAwaitTermination(executorService);