Bootstrap

【Java】Java8新特性:CompletableFuture

Java CompletableFuture 详解

CompletableFuture是函数式异步编程工具,针对Future的一些缺点做的改进,可以通过回调函数的方式实现异步编程。以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法进行异步任务编排以及通用的异常处理机制。

Future的缺点:

  • Future必须阻塞主线程 or 主线程轮询获取结果
  • Future无法很好的实现异步任务间的复杂编排(比如前后依赖、等待全部完成、任一任务完成得到通知等),很难实现纯异步编程
  • 稍复杂的场景使用线程池 + Future 代码可读性更低

CompletableFuture 基本使用

创建CompletableFuture对象提交异步任务

创建一个CompletableFuture对象即创建提交异步任务,创建CompletableFuture对象的方法如下:

  • 静态辅助方法 CompletableFuture.completedFuture
    CompletableFuture.completedFuture是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture
    public static <U> CompletableFuture<U> completedFuture(U value)
    
  • 以下四个静态方法用来为一段异步执行的代码创建CompletableFuture对象
    以Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,线程池默认大小=cpu核数。
    • runAsync: 以Runnable函数式接口类型为参数,所以CompletableFuture计算结果为空

      // 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,线程池默认大小=cpu核数
      public static CompletableFuture<Void> 	runAsync(Runnable runnable)
      // 使用指定的 executor作为它的线程池执行异步代码
      public static CompletableFuture<Void> 	runAsync(Runnable runnable, Executor executor)
      
    • supplyAsync: 以Supplier函数式接口类型为参数,CompletableFuture计算结果类型为U

      // 使用默认ForkJoinPool.commonPool()作为线程池
      public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier)
      // 使用制定的executor作为线程池
      public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier, Executor executor)
      

例子:
因为方法的参数类型都是函数式接口,所以可以使用lambda表达式实现异步任务,比如:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
   
    //长时间的计算任务
    return "·00";
});

获取结果

同步阻塞获取异步结果(不推荐):

CompletableFuture类实现了CompletionStage和Future接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果

public T 	get()
// 阻塞等待指定时间,超时后抛出TimeoutException
public T 	get(long timeout, TimeUnit unit)
//如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值
public T 	getNow(T valueIfAbsent)
// 一直阻塞,直到任务处理结束有结果返回或抛出一个unchecked异常(CompletionException)
public T 	join()

例子:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
   
    int i = 1/0;
    return 100;
});
// 分别调用join和get 看两种对抛出异常的处理的区别
//future.join();
future.get();

主动完成计算

当future在本身线程执行,没有关联任何的callback、线程池、异步任务等,如果客户端调用future.get就会一直等下去。 因此我们可以完成主动的计算,触发客户端:

// 主动完成计算,触发客户端的等待
f.complete(100);
// 抛出一个异常
f.completeExceptionally(new Exception());

CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被调用一次。但是我们有两个后门方法可以重设这个值:obtrudeValue、obtrudeException,但是使用的时候要小心,因为complete已经触发了客户端,有可能导致客户端会得到不期望的结果。


例子:

public class BasicMain {
   
    public static CompletableFuture<Integer> compute() {
   
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        return future;
    }
    public static void main(String[] args) throws Exception {
   
        final CompletableFuture<Integer> f = compute();
        class Client extends Thread {
   
            CompletableFuture<Integer> f;
            Client(String threadName, CompletableFuture<Integer> f) {
   
                super(threadName);
                this.f = f;
            }
            @Override
            public void run() {
   
                try {
   
                    System.out.println(this.getName() + ": " + f.get());
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                } catch (ExecutionException e) {
   
                    e.printStackTrace();
                }
            }
        }
        new Client("Client1", f).start();
        new Client("Client2", f).start();
        System.out.println("waiting");
        f.complete(100);
        //f.completeExceptionally(new Exception());
        System.in.read();
    }
}

计算结果完成时的处理

返回原始cf的计算结果

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:
这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。


                
      
;