异步编程 CompletableFuture详解

1. CompletableFuture概述

在异步编程中,Future/Promise模式是一种广泛使用的异步开发模式,其中 Future 对象代表一个尚未完成异步操作的结果。从JDK 1.5以来,JUC包一直提供着最基本的Future,
但是Futrue对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。
在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能。

CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。
CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理

2. How to use
2.1 创建CompletableFuture

CompletableFuture中有以下几种静态方法可用来创建对象:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                   Executor executor) {
    return asyncSupplyStage(screenExecutor(executor), supplier);
}
public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}
public static CompletableFuture<Void> runAsync(Runnable runnable,
                                               Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
}

以Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。

runAsync方法也好理解,它以Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空。

supplyAsync方法以Supplier<U> 函数式接口类型为参数,CompletableFuture的计算结果类型为U。举个例子

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> task(100) );
CompletableFuture future2 = CompletableFuture.runAsync(() -> {System.out.println("100");} );
2.2 获取结果

CompletableFuture类实现了CompletionStage和Future接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果,尽管这种方式不推荐使用。

public T 	get()
public T 	get(long timeout, TimeUnit unit)
public T 	getNow(T valueIfAbsent)
public T 	join()

getNow有点特殊,如果结果已经计算完则返否则返回给定的alueIfAbsent值。

public class BasicMain {

  public static void main(String[] args) throws Exception {
    final CompletableFuture future = CompletableFuture.supplyAsync(() ->
    {
      try {
        Thread.sleep(1000000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "result";
    });
    future.complete("default result");
    System.out.println(future.getNow("now"));
  }
}

还有两个方法可以设定执行的结果:

  • complete(T t) 完成异步执行,如果任务已经执行,则返回false,如果没执行完,返回true,并设置结果。
  • completeExceptionally(Throwable ex) 异步执行不正常的结束
public class BasicMain {

    public static void main(String[] args) throws Exception {
        final CompletableFuture future = CompletableFuture.supplyAsync(() ->
        {
           /* try {
                Thread.sleep(1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            return "result";
        });
        Thread.sleep(100);
        System.out.println(future.complete("complete result"));
        System.out.println(future.get());
    }
}
2.3 转换操作

CompletableFuture可以作为monad(单子)和functor。由于回调风格的实现,我们不必因为等待一个计算完成而阻塞着调用线程,
而是告诉CompletableFuture当计算完成的时候请执行某个function。而且我们还可以将这些操作串联起来,
或者将CompletableFuture组合起来。

public <U> CompletableFuture<U> 	thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> 	thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> 	thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

这一组函数的功能是当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture计算结果。因此它的功能相当于将CompletableFuture转换成CompletableFuture<\U>。
类似于RxJava中的map。以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行

public class ApplyTest {

  public static void main(String[] args) throws Exception {
    CompletableFuture future = CompletableFuture.supplyAsync(() -> "hello")
        .thenApplyAsync(v -> {
          System.out.println(Thread.currentThread());
          return v + "world";
        })
        .thenApply(v -> {
          System.out.println(Thread.currentThread());
          return v.length();
        });
  }
}
/*结果
Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[main,5,main]
*/

这些转换操作不会阻塞当前线程,但是每一个变换都是在前一个完成后再进行转换的。
注意,这里结果转换是任意的,原型为:

thenApply :: CompletableFuture1 a -> CompletableFuture1 b

有一种情况就是异步执行的内容又是一个异步任务,它返回CompletableFuture类型。
如下代码:

public class ComposeTest {

  public static void main(String[] args) throws ExecutionException, InterruptedException {
    // String -> CompletableFuture
    CompletableFuture future1 = CompletableFuture.supplyAsync(() -> "hello")
      .thenApply(v -> CompletableFuture.supplyAsync(() -> v + "apply"));
    // String -> CompletableFuture -> String
    CompletableFuture future2 = CompletableFuture.supplyAsync(() -> "Hello")
        .thenCompose(v -> CompletableFuture.supplyAsync(() -> v + " compose"));
    System.out.println(future1.get());
    System.out.println(future2.get());
  }
}
/* 运行结果
java.util.concurrent.CompletableFuture@1d0d5ae[Completed normally]
Hello compose
*/

可以直观的看出来,thenCompose方法将和thenApply的区别,thenApply单纯的就是对结果的转换,而thenCompose会对结果
有一个“去壳”的操作。

public <U> CompletableFuture<U> thenCompose( Function<? super T, ? extends CompletionStage<U>> fn) {
    return uniComposeStage(null, fn);
}

它和thenApply的参数是不同的,Function接口的方法apply的返回值不同,thenApply中可以返回任意类型,而在thenCompose中只能返回CompletionStage类型,
而实现了该接口的只有CompletableFuture,正因如此,内部才会进行“去壳”操作。此操作类似于RxJava中Flatamap。函数原型为

thenCompose :: CompletableFuture a -> (a -> CompletableFuture b) -> CompletableFuture b

2.4 组合操作

CompletableFuture类里面还有个thenCombine操作,

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) {
    return biApplyStage(null, other, fn);
}

该方法整合两个异步计算的结果
举个例子:

public class CombindTest {

  public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "length:");
    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 100);
    CompletableFuture<String> future = future1
        .thenCombine(future2, (s, i) -> s + i);
    System.out.println(future.get());
  }
}
2.5 完成时处理

当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:

public CompletableFuture<T> 	whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> 	whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> 	whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T>   exceptionally(Function<Throwable,? extends T> fn)

这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。

public class CompleteTest {

  public static void main(String[] args) {
    CompletableFuture.supplyAsync(() -> 100)
        //.thenApply(s-> s/10)
        .thenApply(s-> s/0)
        .whenComplete((result, throwable) -> System.out.println("result:" + result+"\nerror:"+throwable));

  }
}
/*
result:10
error:null

result:null
error:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
*/

可以看出,whenComplete()的参数是BiConsumer,accept()方法返回void。也就是说它不能修改结果,说白了就是只能对结果读。因此就有了handle的存在

handle(BiFunction<? super T, Throwable, ? extends U> fn)

handle()的参数是BiFunction,apply()方法返回R,相当于转换的操作。

CompletableFuture还提供了一种处理结果的方法,只对结果执行Action,并且消费结果。
它们是函数式接口Consumer,这个接口只有输入,没有返回值。

public CompletableFuture<Void> 	thenAccept(Consumer<? super T> action)
public class AcceptTest {

  public static void main(String[] args) {
    CompletableFuture.supplyAsync(() -> "hello")
        .thenAccept(System.out::println)
        .whenComplete((r, t) -> System.out.println("complete:" + r));
  }
}
/* 结果
hello
complete:null
*/
2.6 异常处理

CompletableFuture在运行时如果遇到异常,可以使用get()并抛出异常进行处理,但这并不是一个最好的方法。CompletableFuture本身也提供了几种方式来处理异常。

public CompletableFuture<T> exceptionally(
        Function<Throwable, ? extends T> fn) 

只有当CompletableFuture抛出异常的时候,才会触发这个exceptionally的计算,调用function计算值

public class ExcepptionTest {

  public static void main(String[] args) {
    CompletableFuture.supplyAsync(() -> 100)
        //.thenApplyAsync(t -> t / 0)
        .exceptionally(t -> {
          System.out.println(t);
          return null;
        });
  }
}

还有一种处理异常的办法是whenComplete,在前面已经用到过了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值