Читаем Многопоточное программирование в Java полностью

То есть этот метод не создает никакого нового пула потоков, а отправляет нас к фреймворку fork/join.

Помимо классов RecursiveAction и RecursiveTask, Java 8 вводит класс CountedCompleter, который реализует класс ForkJoinTask.

Класс CountedCompleter обеспечивает механизм для выполнения метода после завершения всех подзадач.

Это метод onCompletion класса CountedCompleter.

Для вызова метода onCompletion, в той части метода compute, где идет вычисление подзадачи, достигшей минимального размера, мы вызываем метод tryComplete, который вызывает метод onCompletion.

Когда задача завершает метод onCompletion, вызывается метод tryComplete родительской подзадачи, и так далее до источника.

Если необходимо вернуть результат, переопределяется метод getRawResult класса CountedCompleter.

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

Благодаря этому изменяется внутренний счетчик ожидающих задач.

Этот счетчик определяет, были ли выполнены задачи.

Если это не так, тогда счетчик уменьшается.

Когда задачи завершаются и счетчик обнуляется и вызывается метод tryComplete, событие завершения отправляется во все задачи CountedCompleter.

Если метод tryComplete не будет вызван перед возвратом, задача будет считаться незавершенной и будет выполняться неопределенно долго.

<p>CompletableFuture</p>

Ранее мы использовали объект Future для получения результата выполнения задачи Callable в отдельном потоке.

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

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

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

Он предоставлял метод isDone, чтобы проверить, выполнено ли вычисление или нет, и метод get для получения результата вычисления при его выполнении.

Однако интерфейс Future имеет некоторые недостатки.

При его использовании результат вычисления невозможно завершить вручную.

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

Так как этот вызов занимает много времени, вы запускаете его в отдельном потоке и возвращаете Future из своей функции.

Предположим теперь, что, если удаленный сервис недоступен, вы хотите завершить Future вручную с кэшированными данными.

Сделать это с помощью Future нельзя.

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

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

И у вас нет возможности подключить функцию обратного вызова к Future и получить ее вызов автоматически, когда будет получен результат в будущем.

Далее, нельзя соединить вместе несколько объектов Future.

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

Вы не можете создать такой асинхронный рабочий поток с помощью Future.

Далее, вы не можете комбинировать несколько объектов Future вместе.

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

Вы тоже не можете это сделать с помощью интерфейса Future.

Кроме того, интерфейс Future не имеет конструкции обработки исключений.

Все эти проблемы решает класс CompletedFuture, который реализует интерфейсы Future и CompletionStage и предоставляет набор методов для создания, соединения и объединения нескольких Future. Он также поддерживает обработку исключений.

Для выполнения задачи в отдельном потоке, мы применяем статический метод supplyAsync, который принимает реализацию интерфейса Supplier и выполняет эту реализацию в потоке пула потоков ForkJoinPool.

Интерфейс Supplier имеет единственный метод get, который необходимо определить.

Если вы хотите принудительно завершить вычисление, вы вызываете метод complete объекта CompletableFuture и возвращаете результат, который вы указали в качестве аргумента метода.

Если вы хотите выполнить задачу без возврата результата, вы вызываете метод runAsync и передаете ему объект Runnable.

Также вы можете выполнить задачу в каком-либо другом пуле потоков, явно указав Executor.

Пока что мы не видим явных преимуществ перед Future, так как для получения результата мы просто вызываем блокирующий метод get.

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

И мы сможем написать логику, которая должна быть выполнена после завершения Future внутри нашей функции обратного вызова.

Можно присоединить обратный вызов к CompletableFuture, используя методы thenApply, thenAccept и thenRun.

Перейти на страницу:

Похожие книги