CompletableFuture asynchronous computing


Outline

1.8 newly added internal CompletableFuture Java classes using ForkJoinPool implemented, CompletableFuture realized Future interfaces and CompletionStage interface.

Before the introduction of the Future and basic usage article, we learned Future represents the result of an asynchronous computation.

CompletionStage represents a particular computing stage can be synchronous or asynchronous is completed.

CompletionStage can be seen as a single unit on the assembly line computing will eventually produce a final result, which means that several CompletionStage can be connected in series, a stage of completion can trigger the execution of the next stage, then trigger the next, until all stage.

Future drawbacks

Future classes is added Java 5, used to describe the results of an asynchronous computation. You can use isDone method to check whether the calculation is completed, or live with get blocked calling thread until the calculation is complete return the results, you can also use the cancel method to stop performing a task.

Although the Future and the associated use of asynchronous method provides the ability to perform tasks, but to get results is very convenient, the task can only get the results by polling or blocking the way. Blocking the way is clear and our asynchronous programming contrary to the original intention, by way of polling will consume unnecessary CPU resources, and can not get timely results.

package net.ijiangtao.tech.concurrent.jsd.future.completable;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * java5 future
 *
 * @author ijiangtao
 * @create 2019-07-22 9:40
 **/
public class Java5Future {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //通过 while 循环等待异步计算处理成功
        ExecutorService pool = Executors.newFixedThreadPool(10);
        Future<Integer> f1 = pool.submit(() -> {
            // 长时间的异步计算 ……
            Thread.sleep(1);
            // 然后返回结果
            return 1001;
        });

        while (!f1.isDone())
            System.out.println("is not done yet");
        ;
        System.out.println("while isDone,result=" + f1.get());

        //通过阻塞的方式等待异步处理成功
        Future<Integer> f2 = pool.submit(() -> {
            // 长时间的异步计算 ……
            Thread.sleep(1);
            // 然后返回结果
            return 1002;
        });
        System.out.println("after blocking,result=" + f2.get());
    }

}
复制代码

CompletableFuture method

We mentioned CompletableFuture provides asynchronous calculation for us, and these are achieved through the realization of its methods.

If you open the document it CompletableFuture-Java8Docs , you will find CompletableFuture provided nearly 60 method. Although many ways, if you look closely, you will have these methods are many similarities.

As long as you master these methods, it can be handy to use CompletableFuture to the asynchronous computation.

Java class of CompletableFuture always follow this principle:

  • Threaded calculation method name does not end with the original method Async
  • The method of method names and parameters are not Async end Executor calculated by the default thread pool ForkJoinPool.commonPool ()
  • The method of method names and parameters is Async end Executor calculated by the specified thread pool Executor

The following will not see them here.

supplyAsync ... asynchronous computation results

return value Method name and parameters Method Description
static CompletableFuture supplyAsync(Supplier supplier) Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() with the value obtained by calling the given Supplier.
static CompletableFuture supplyAsync(Supplier supplier, Executor executor) Returns a new CompletableFuture that is asynchronously completed by a task running in the given executor with the value obtained by calling the given Supplier.

Methods supplyAsync Supplier Function Interface Type parameter is calculated CompletableFuture result type is U. Since the process parameters are a function of the type of interface, a lambda expression can be used to achieve asynchronous tasks. Explain other methods later, when the child would be an example.

runAsync method Ye appreciated it Runnable interface function parameter type, the calculation result is also null CompletableFuture (Runnable run method returns null value). Here is no longer introduced one by one, little interested partners can view the API documentation.

get ... blocked results

return value Method name and parameters Method Description
T get() Waits if necessary for this future to complete, and then returns its result.
T get(long timeout, TimeUnit unit) Waits if necessary for at most the given time for this future to complete, and then returns its result, if available.
T getNow(T valueIfAbsent) Returns the result value (or throws any encountered exception) if completed, else returns the given valueIfAbsent.
T join() Returns the result value when complete, or throws an (unchecked) exception if completed exceptionally.

You can use the same as using Future CompletableFuture, to blocking calculations (although not recommended). Wherein getNow special: the end of the calculation result has a result or throw an exception is returned, otherwise the given value valueIfAbsent.

join and get to the calculation methods can be completed blocked returns the result then obtained, but both differ from the process flow, refer to the following piece of code to compare the difference between them at the processing exception:

  public static void main(String[] args) {
        try {
            new CompletableFutureDemo().test2();
            new CompletableFutureDemo().test3();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void test2() throws Exception {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            int i = 1 / 0;
            return 100;
        });
        future.join();
    }

    public void test3() throws Exception {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            int i = 1 / 0;
            return 100;
        });
        future.get();
    }
复制代码

Output is as follows:

java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)
	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.ArithmeticException: / by zero
	at net.ijiangtao.tech.concurrent.jsd.future.completable.CompletableFutureDemo.lambda$test2$0(CompletableFutureDemo.java:32)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
	... 5 more
复制代码

thenApply ... conversion results

return value Method name and parameters Method Description
CompletableFuture thenApply(Function<? super T,? extends U> fn) Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied function.
CompletableFuture thenApplyAsync(Function<? super T,? extends U> fn) Returns a new CompletionStage that, when this stage completes normally, is executed using this stage's default asynchronous execution facility, with this stage's result as the argument to the supplied function.
CompletableFuture thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) Returns a new CompletionStage that, when this stage completes normally, is executed using the supplied Executor, with this stage's result as the argument to the supplied function.

Use CompletableFuture, because we do not have to wait for a complete computing and blocking the calling thread but to tell CompletableFuture when the calculation is complete when do one function. And we also can use these operations together, or in combination CompletableFuture.

This function is the set of functions when the original CompletableFuture after computing, pass the results to the function fn, fn is the result of calculation result as a new CompletableFuture. Thus its function is equivalent to converting CompletableFuture CompletableFuture .

Consider the following example:

  public static void main(String[] args) throws Exception {
        try {
            // new CompletableFutureDemo().test1();
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            //new CompletableFutureDemo().test2();
            //new CompletableFutureDemo().test3();
        } catch (Exception e) {
            e.printStackTrace();
        }

        new CompletableFutureDemo().test4();

    }

    public void test4() throws Exception {
        // Create a CompletableFuture
        CompletableFuture<Integer> calculateFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            System.out.println("2");
            return 1 + 2;
        });

        // Attach a callback to the Future using thenApply()
        CompletableFuture<String> resultFuture = calculateFuture.thenApply(number -> {
            System.out.println("3");
            return "1 + 2 is " + number;
        });

        // Block and get the result of the future.
        System.out.println(resultFuture.get());
    }
复制代码

The output is:

1
2
3
1 + 2 is 3
复制代码

thenAccept ... pure consumption results

return value Method name and parameters Method Description
CompletableFuture thenAccept(Consumer<? super T> action) Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied action.
CompletableFuture thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action) Returns a new CompletionStage that, when this and the other given stage both complete normally, is executed with the two results as arguments to the supplied action.

篇幅原因,Async和Executor方法不再列举。

只对结果执行Action,而不返回新的计算值,因此计算值为Void。这就好像生产者生产了消息,消费者消费消息以后不再进行消息的生产一样,因此thenAccept是对计算结果的纯消费。

例如如下方法:

public void test5() throws Exception {
    // thenAccept() example
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
        return "ijiangtao";
    }).thenAccept(name -> {
        System.out.println("Hi, " + name);
    });
    System.out.println(future.get());
}
复制代码

thenAccept的get返回为null:

Hi, ijiangtao
null
复制代码

thenAcceptBoth可以消费两者(生产和消费)的结果,下面提供一个例子:

 public void test6() throws Exception {
    CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ijiangtao";
    }), (s1, s2) -> {
        System.out.println(s1 + " " + s2);
    });

    while (true){

    }
}
复制代码

输出如下:

hello ijiangtao
复制代码

thenCombine ... 消费结果并返回

返回值 方法名及参数 方法说明
<U,V> CompletableFuture thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) Returns a new CompletionStage that, when this and the other given stage both complete normally, is executed with the two results as arguments to the supplied function.
<U,V> CompletableFuture thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) Returns a new CompletionStage that, when this and the other given stage complete normally, is executed using this stage's default asynchronous execution facility, with the two results as arguments to the supplied function.

从功能上来讲, thenCombine的功能更类似thenAcceptBoth,只不过thenAcceptBoth是纯消费,它的函数参数没有返回值,而thenCombine的函数参数fn有返回值。

public void test7() throws Exception {
    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        return 1+2;
    });
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        return "1+2 is";
    });
    CompletableFuture<String> f = future1.thenCombine(future2, (x, y) -> y + " " + x);
    System.out.println(f.get()); // 输出:1+2 is 3
}
复制代码

thenCompose ... 非嵌套整合

返回值 方法名及参数 方法说明
CompletableFuture thenCompose(Function<? super T,? extends CompletionStage> fn) Returns a new CompletionStage that, when this stage completes normally, is executed with this stage as the argument to the supplied function.

由于篇幅原因,Async和Executor方法不再列举。

thenCompose方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture。

加入你需要将两个CompletableFutures相互整合,如果使用thenApply,则结果会是嵌套的CompletableFuture:

CompletableFuture<String> getUsersName(Long id) {
    return CompletableFuture.supplyAsync(() -> {
        return "ijiangtao";
    });
}

CompletableFuture<Integer> getUserAge(String userName) {
    return CompletableFuture.supplyAsync(() -> {
        return 20;
    });
}

public void test8(Long id) throws Exception {
    CompletableFuture<CompletableFuture<Integer>> result1 = getUsersName(id)
            .thenApply(userName -> getUserAge(userName));
}
复制代码

这时候可以使用thenCompose来获得第二个计算的CompletableFuture:

public void test9(Long id) throws Exception {
    CompletableFuture<Integer> result2 = getUsersName(id)
            .thenCompose(userName -> getUserAge(userName));
}
复制代码

thenCompose ... 非嵌套整合

返回值 方法名及参数 方法说明
CompletableFuture whenComplete(BiConsumer<? super T,? super Throwable> action) Returns a new CompletionStage with the same result or exception as this stage, that executes the given action when this stage completes.
CompletableFuture whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) Returns a new CompletionStage with the same result or exception as this stage, that executes the given action using this stage's default asynchronous execution facility when this stage completes.
CompletableFuture whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor) Returns a new CompletionStage with the same result or exception as this stage, that executes the given action using the supplied Executor when this stage completes.

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。whenComplete的参数Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。注意这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。

whenComplete方法不以Async结尾,意味着Action使用相同的线程执行,而以Async结尾的方法可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

下面演示一下异常情况:

public void test10() throws Exception {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (1 == 1) {
            throw new RuntimeException("an RuntimeException");
        }
        return "s1";
    }).whenComplete((s, t) -> {
        System.out.println("whenComplete s:"+s);
        System.out.println("whenComplete exception:"+t.getMessage());
    }).exceptionally(e -> {
        System.out.println("exceptionally exception:"+e.getMessage());
        return "hello ijiangtao";
    }).join();

    System.out.println(result);
}
复制代码

输出:

whenComplete s:null
whenComplete exception:java.lang.RuntimeException: an RuntimeException
exceptionally exception:java.lang.RuntimeException: an RuntimeException
hello ijiangtao
复制代码

总结

Java5新增的Future类,可以实现阻塞式的异步计算,但这种阻塞的方式显然和我们的异步编程的初衷相违背。为了解决这个问题,JDK吸收了Guava的设计思想,加入了Future的诸多扩展功能形成了CompletableFuture。

本文重点介绍了CompletableFuture的不同类型的API,掌握了这些API对于使用非阻塞的函数式异步编程进行日常开发非常有帮助,同时也为下面深入了解异步编程的各种原理和特性打下了良好的基础。

相关资源

CompletableFuture - javase 8 docs

CompletableFuture - Guide To CompletableFuture

CompletableFuture - Java CompletableFuture Tutorial with Examples

CompletableFuture - Java 8: Writing asynchronous code with CompletableFuture

《写给大忙人的JavaSE9核心技术》- 10.2 异步计算

CompletableFuture - 通过实例理解 JDK8 的 CompletableFuture

CompletableFuture - CompletableFuture 详解

CompletableFuture - 使用 Java 8 的 CompletableFuture 实现函数式的回调

CompletableFuture - Java CompletableFuture 详解

CompletableFuture - [译]20个使用 Java CompletableFuture的例子


Wechat-westcall

Guess you like

Origin juejin.im/post/5d35d6ebe51d45109b01b270