Новый метод асинхронного программирования Java8 CompletableFuture (1)

Java

1. Будущее

JDK 5 представил режим будущего. Интерфейс Future представляет собой реализацию многопоточного режима Future в Java.В пакете java.util.concurrent можно выполнять асинхронные вычисления.

Шаблон Future — это шаблон проектирования, обычно используемый в многопоточном проектировании. Режим Future можно понимать так: у меня есть задача, и я отправляю ее в Future, а Future выполняет эту задачу за меня. А пока я могу делать все, что захочу. Через некоторое время я смогу получить результаты из Будущего.

Интерфейс Future очень прост, всего пять методов.

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Методы интерфейса Future описываются следующим образом:

  • boolean cancel (boolean mayInterruptIfRunning) Отменяет выполнение задачи. Параметр указывает, следует ли немедленно прервать выполнение задачи или дождаться завершения задачи.
  • boolean isCancelled () Была ли задача отменена, если задача отменена до ее нормального завершения, она вернет true
  • boolean isDone () Завершена ли задача. Следует отметить, что если задача завершена нормально, аварийно или отменена, она вернет true
  • V get() выбрасывает InterruptedException, ExecutionException Дождитесь окончания выполнения задачи, после чего получите результат типа V. Поток InterruptedException является прерванным исключением, исключение выполнения задачи ExecutionException, если задача отменена, также будет выбрано CancellationException
  • V get (длительный тайм-аут, модуль TimeUnit) генерирует InterruptedException, ExecutionException, TimeoutException То же, что и функция get выше, с добавлением установки времени тайм-аута. Параметр timeout указывает время ожидания, а uint указывает единицу времени, которая определена в классе перечисления TimeUnit. Если время вычисления истекло, будет выброшено исключение TimeoutException.

В обычных обстоятельствах мы будем использовать Callable и Future вместе, выполнять Callable через метод отправки ExecutorService и возвращать Future.

        ExecutorService executor = Executors.newCachedThreadPool();

        Future<String> future = executor.submit(() -> { //Lambda 是一个 callable, 提交后便立即执行,这里返回的是 FutureTask 实例
            System.out.println("running task");
            Thread.sleep(10000);
            return "return task";
        });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        System.out.println("do something else");  //前面的的 Callable 在其他线程中运行着,可以做一些其他的事情

        try {
            System.out.println(future.get());  //等待 future 的执行结果,执行完毕之后打印出来
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {

        } finally {
            executor.shutdown();
        }

По сравнению с future.get() на самом деле более рекомендуется использовать метод get (длинный тайм-аут, блок TimeUnit).Установка тайм-аута может предотвратить бесконечное ожидание программой результата будущего.

2. Введение в CompletableFuture

2.1 Недостатки будущего шаблона

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

  • Либо используйте блокировку, либо дождитесь результата, возвращаемого future в future.get(), который затем становится синхронной операцией. Или используйте isDone() для опроса, чтобы определить, завершено ли Future, что потребует ресурсов ЦП.

2.2 Введение в CompletableFuture

Netty и Guava соответственно расширяют интерфейс Java Future для облегчения асинхронного программирования.

Новый класс CompletableFuture Java 8 поглощает все возможности ListenableFuture и SettableFuture в Google Guava, а также предоставляет другие мощные функции, позволяющие Java иметь полную неблокирующую модель программирования: Future, Promise и Callback (до Java8 только Future без Перезвони).

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

CompletableFuture компенсирует недостатки шаблона Future. После завершения асинхронной задачи вам не нужно ждать, когда вам нужно продолжить работу с ее результатом. Результат предыдущей асинхронной обработки может быть передан другому потоку асинхронной обработки событий для обработки непосредственно через thenAccept, thenApply, thenCompose и т. д.

3. Функции CompletableFuture

3.1 Статический фабричный метод CompletableFuture

имя метода описывать
runAsync(Runnable runnable) Выполнение асинхронного кода с использованием ForkJoinPool.commonPool() в качестве пула потоков.
runAsync(Runnable runnable, Executor executor) Выполнение асинхронного кода с использованием указанного пула потоков.
supplyAsync(Supplier<U> supplier) Используйте ForkJoinPool.commonPool() в качестве пула потоков для выполнения асинхронного кода, а асинхронные операции имеют возвращаемые значения.
supplyAsync(Supplier<U> supplier, Executor executor) Используйте указанный пул потоков для выполнения асинхронного кода, и асинхронная операция имеет возвращаемое значение.

Разница между методами runAsync и SupplyAsync заключается в том, что CompletableFuture, возвращаемый runAsync, не имеет возвращаемого значения.

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("Hello");
        });

        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("CompletableFuture");

CompletableFuture, возвращаемый SupplyAsync, определяется возвращаемым значением. Следующий код выводит возвращаемое значение future.

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("CompletableFuture");

3.2 Completable

имя метода описывать
complete(T t) Завершите асинхронное выполнение и верните результат будущего
completeExceptionally(Throwable ex) Аномальное завершение асинхронного выполнения

Когда future.get() ожидает результата выполнения, программа всегда блокируется, и если в это время вызывается complete(T t), она будет выполнена немедленно.

        CompletableFuture<String> future  = CompletableFuture.supplyAsync(() -> "Hello");

        future.complete("World");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

Результаты:

World

Вы можете видеть, что будущий вызов complete(T t) будет выполнен немедленно. Но метод complete(T t) может быть вызван только один раз, и последующие повторные вызовы будут недействительны.

Если future уже был выполнен и может вернуть результат, вызов complete(T t) в это время будет недействителен.

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        future.complete("World");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

Результаты:

Hello

Использование completeExceptionally(Throwable ex) вызывает исключение вместо успешного результата.

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        future.completeExceptionally(new Exception());

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

Результаты:

java.util.concurrent.ExecutionException: java.lang.Exception
...