【java并发工具-分工】CompletableFuture实现异步编程

CompletableFuture

  • 前言
  • 1.创建CompletableFuture对象
  • 2.CompletionStage接口
    • 2.1如何理解CompletionStage接口
      • 2.1.1 描述串行关系
      • 2.1.2 描述AND汇聚关系
      • 2.1.3 描述OR汇聚关系
    • 2.2 CompletionStage的异常处理

前言

一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。 Java 提供 Runnable Future 两个接口用来实现异步任务逻辑。

虽然 Future 可以获取任务执行结果,但是获取方式十方不变。我们不得不使用Future#get 阻塞调用线程,或者使用轮询方式判断 Future#isDone 任务是否结束,再获取结果。

1.创建CompletableFuture对象

创建CompletableFuture对象主要靠下面4个静态方法:

//使用默认线程池(ForkJoinPool 线程池)
static CompletableFuture<Void>   runAsync(Runnable runnable)		//它的run方法没有返回值
static <U> CompletableFuture<U>   supplyAsync(Supplier<U> supplier)		//而Supplier接口的get方法是有返回值的。
//上面2个方法和下面两个方法的区别就是--可以指定线程池  
static CompletableFuture<Void>   runAsync(Runnable runnable, Executor executor)
static <U> CompletableFuture<U>   supplyAsync(Supplier<U> supplier, Executor executor)  

创建出CompletableFuture对象后,会自动异步执行runnable.run()方法,或者supplier.get()方法。

对于异步编程需要注意两个问题:

  1. 一个异步操作什么时候结束;
  2. 如何获取异步操作的执行结果;

如何解决呢?
可以看出CompletableFuture实现了Future接口,可以通过Future接口来解决。

另外CompletableFuture类还实现了CompletionStage接口,里面有40个方法,哈哈。

2.CompletionStage接口

2.1如何理解CompletionStage接口

站在分工的角度,把任务分为串行关系,并行关系,汇聚关系
【java并发工具-分工】CompletableFuture实现异步编程_第1张图片

CompletionStage接口可以清晰地描述这种任务之间的关系。

2.1.1 描述串行关系

//串行关系的方法主要是下面这几个:
CompletionStage<R> thenApply(fn);
CompletionStage<R> thenApplyAsync(fn);
CompletionStage<Void> thenAccept(consumer);
CompletionStage<Void> thenAcceptAsync(consumer);
CompletionStage<Void> thenRun(action);
CompletionStage<Void> thenRunAsync(action);
CompletionStage<R> thenCompose(fn);
CompletionStage<R> thenComposeAsync(fn);
  • thenApply系列函数中参数fn的类型是接口Function,这个接口里与CompletionStage相关的方法是R apply(T t),所以看到这个方法支持传入参数,同时支持返回值,所以thenapply返回的是CompletionStage。
  • thenAccept系列函数中参数consumer类型的接口是Consumer,这个接口中与CompletionStage相关的方法是 void accept(T t),这个方法支持参数,但不支持返回值,所以thenAccept方法返回的是CompletionStage。
  • thenRun 系列方法里 action 的参数是 Runnable,所以 action 既不能接收参数也不支持返回值,所以 thenRun 系列方法返回的也是CompletionStage。
  • thenCompose 系列方法,这个系列的方法会新创建出一个子流程,最终结果和 thenApply 系列是相同的。

以上方法Async代表的是异步执行fn,consumer或者action,相反就是同步执行了。

下面用实例代码展示:
supplyAsync先启动一个异步流程,之后2,3是串行操作,虽然这是一个异步流程,但是1,2,3是有序串行操作的。

CompletableFuture<String> f0 =   CompletableFuture.supplyAsync(
    () -> "Hello World")      //①
  .thenApply(s -> s + " QQ")  //②
  .thenApply(String::toUpperCase);//③

System.out.println(f0.join());
//输出结果
HELLO WORLD QQ

2.1.2 描述AND汇聚关系

//下面是描述AND汇聚关系的CompletionStage接口:
CompletionStage<R> thenCombine(other, fn);
CompletionStage<R> thenCombineAsync(other, fn);
CompletionStage<Void> thenAcceptBoth(other, consumer);
CompletionStage<Void> thenAcceptBothAsync(other, consumer);
CompletionStage<Void> runAfterBoth(other, action);
CompletionStage<Void> runAfterBothAsync(other, action);

上面这些接口的区别也是fn,consumer,action三个参数不同。

下面用实例代码展示(烧开水操作和拿茶叶操作必须都实现了才能执行泡茶):

CompletableFuture<String> task1 =   CompletableFuture.supplyAsync(()->{
sleep(3,TimeUnit.SECONDS);
return "烧开水";
});
CompletableFuture<String> task2 =   CompletableFuture.supplyAsync(()->{
sleep(3,TimeUnit.SECONDS);
return "拿茶叶";
});
//任务3必须在任务1和任务2都执行结束才能执行。
CompletableFuture<String> task3 =   task2.thenCombine(task1, (shaokaishui,nacha)->{
sleep(3,TimeUnit.SECONDS);
return "泡茶";
});
System.out.println(task3.join());

2.1.3 描述OR汇聚关系

OR汇聚关系不知道怎么画,就是当两个任务中只要有一个任务执行完成,就可以接着执行下一任务。

//下面是表示这个关系的CompletionStage的接口方法
CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);

区别也是fn,consumer,action三个参数的不同。
下面是实例代码:

CompletableFuture<String> f1 =   CompletableFuture.supplyAsync(()->{
    int t = getRandom(5, 10);
    sleep(t, TimeUnit.SECONDS);
    return String.valueOf(t);
});
CompletableFuture<String> f2 =   CompletableFuture.supplyAsync(()->{
    int t = getRandom(5, 10);
    sleep(t, TimeUnit.SECONDS);
    return String.valueOf(t);
});
CompletableFuture<String> f3 =   f1.applyToEither(f2,s -> s);
System.out.println(f3.join());

2.2 CompletionStage的异常处理

虽然上面我们提到的fn,consumer,action它们的核心方法都不允许抛出可检查异常,但是却无法限制抛出运行时异常。

非异步编程里面,我们可以使用 try{}catch{}来捕获并处理异常,那在异步编程里面,异常该如何处理呢?例如下面这个异常。

CompletableFuture<Integer>   f0 = CompletableFuture.supplyAsync(()->(7/0))
.thenApply(r->r*10);
System.out.println(f0.join());

CompletionStage 接口给我们提供的方案非常简单,比 try{}catch{}还要简单,下面是相关的方法,使用这些方法进行异常处理和串行操作是一样的,都支持链式编程方式。

CompletionStage exceptionally(fn);
CompletionStage<R> whenComplete(consumer);
CompletionStage<R> whenCompleteAsync(consumer);
CompletionStage<R> handle(fn);
CompletionStage<R> handleAsync(fn);

exceptionally的使用非常类似于try{}catch{}中的catch{}。
既然有try{}catch{},那么一定有try{}finally{},whenComplete和handle系列类似于finally{},无论是否有异常,whenComplete都会执行consumer的回调函数consumer,而handle执行回调函数fn。两者的区别是whenComplete() 不支持返回结果,而 handle() 是支持返回结果的。

代码实例(展示如何使用execptionally处理异常):

CompletableFuture<Integer>  f0 = CompletableFuture
    .supplyAsync(()->7/0))
    .thenApply(r->r*10)
    .exceptionally(e->0);
System.out.println(f0.join());

你可能感兴趣的:(并发编程体系架构,#,java并发工具类)