Обработка задач в Java без блокировки: исполнители и Future

Пройдите тест, узнайте какой профессии подходите и получите бесплатную карьерную консультацию
В конце подарим скидку до 55% на обучение
Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Хотите получать уведомления о завершении задач в ExecutorService без потери работы основного потока? Воспользуйтесь колбэками из CompletableFuture. Используйте thenRun, чтобы добавить колбэк без результатов выполнения задачи, или thenAccept, чтобы обработать результат выполненной задачи.

Java
Скопировать код
ExecutorService executor = Executors.newCachedThreadPool();
CompletableFuture<Integer> futureTask = CompletableFuture.supplyAsync(() -> 42, executor);

futureTask.thenAccept(result -> System.out.println("Результат: " + result));

Здесь выполняется задача, возвращающая 42, через сервис ExecutorService. Когда результат становится доступным, функция thenAccept выведет его, при этом основной поток программы не будет затронут.

Расширение возможностей с помощью CompletableFuture

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

Добавление простых колбэков к задачам

Чтобы выполнять дополнительное действие по завершении задачи, можно применить методы thenRun или thenAccept.

Java
Скопировать код
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // Логика привязанной задачи...
}).thenRun(() -> {
    // Логика колбэка, выполняемого после завершения задачи...
});

Работа с колбэками в многозадачности

Методы thenCompose и thenCombine удобны для совмещения зависимых задач.

Java
Скопировать код
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> "Привет")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + ", Мир!"));
// "Мир!" появится только после "Привета".

Обработка исключений при работе с колбэками

Непредсказуемые ситуации можно обработать с помощью exceptionally.

Java
Скопировать код
CompletableFuture<Integer> future = CompletableFuture
    .supplyAsync(() -> { throw new RuntimeException("Ошибка!"); })
    .exceptionally(ex -> 0); // Подменяем исключение на ноль

Мгновенный колбэк по завершении задачи

Настройте ваш метод done(), используя наследование от FutureTask.

Java
Скопировать код
FutureTask<Integer> futureTask = new FutureTask<>(() -> 42) {
    @Override
    protected void done() {
        // Код для обработки окончания выполнения задачи
    }
};
executor.execute(futureTask);

Визуализация

Представьте задачи как Гоночную трассу:

Markdown
Скопировать код
ExecutorService 🏁: [Задача 1 🚀, Задача 2 🚀, Задача 3 🚀]
CompletionService 🏎️🏁: [Задача 1 🏆, Задача 2 ❓, Задача 3 ❓]

CompletionService действует в роли наблюдателя, сообщая о завершении каждой задачи.

Markdown
Скопировать код
🚀 Задача 1 завершена: 🏎️🏁🏆 "Задача 1 выполнена!"
🚀 Задача 3 опередила: 🏎️🏁🏆 "Задача 3 стала легендой!"

CompletionService позволяет получать результаты по мере их готовности, не блокируя основной поток программы.

Обогащение инструментария с помощью колбэков

Мощь ListenableFuture от Guava

ListenableFuture и ListeningExecutorService от Guava предоставляют расширенный контроль над асинхронными задачами. Они позволяют регистрировать колбэки.

Java
Скопировать код
ListeningExecutorService service = MoreExecutors.listeningDecorator(executor);
ListenableFuture<Integer> future = service.submit(() -> 42);

Futures.addCallback(future, new FutureCallback<Integer>() {
    public void onSuccess(Integer result) {
        // Код при успешном выполнении
    }
    public void onFailure(Throwable thrown) {
        // Код при возникновении ошибки
    }
}, service);

Хуки ThreadPoolExecutor для отслеживания задач

Для получения информации о задачах или выполнения действий перед и после задач используйте хуки beforeExecute и afterExecute.

Java
Скопировать код
ThreadPoolExecutor executor = new ThreadPoolExecutor(...) {
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        // Код перед выполнением задачи
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        // Код после выполнения задачи
    }
};

Стабильная производительность с определенным пулом потоков

Для большей уверенности в производительности используйте фиксированный пул потоков.

Java
Скопировать код
ExecutorService fixedExecutor = Executors.newFixedThreadPool(10);

Действия после окончания работы ExecutorService

При завершении работы ExecutorService можно вызвать метод terminated().

Java
Скопировать код
ThreadPoolExecutor executor = new ThreadPoolExecutor(...) {
    @Override
    protected void terminated() {
        // Код после окончания работы ExecutorService
    }
};

Искусное организование задач и колбэков

Структурируйте асинхронные задачи и колбэки с помощью интерфейса Callable, используйте структуру try-finally для освобождения ресурсов внутри задачи, постобработку результатов выполняйте через thenApply, а при возникновении исключений восстанавливайте цепочку с помощью метода exceptionally.

Полезные материалы

  1. Callable (Java Platform SE 8 ) – Подробный разбор интерфейса Callable.
  2. CompletableFuture (Java Platform SE 8 ) – Проникновение в CompletableFuture и усвоение принципов асинхронного программирования.
  3. Executors (Java Platform SE 8 ) – Руководство по фреймворку исполнения в Java и его вспомогательным методам.
  4. java – How to wait for all threads to finish, using ExecutorService? – Stack Overflow – Советы по управлению завершением потоков с использованием ExecutorService.
  5. Shutting down Threads Cleanly – Стильное завершение работы потоков и ExecutorCompletionService.
  6. Medium – Начальный курс по основам многопоточности в Java 8.