CompletableFuture的使用

131 阅读6分钟

以下内容参考《Java并发编程深度解析与实战》谭锋(第11章)

为什么要用 CompletableFuture

使用 CompletableFuture 是因为 Future 虽然可以获取线程执行结果,但是无法使用回调函数,只能依靠阻塞方法 get() 来获取线程结果,CompletableFuture 就是对 Future 的优化和增强,具体可以实现以下功能

  1. 提供了类似 Future 的阻塞式获取结果和状态的方法
  2. 提供 CompletionStage 任务执行之后回调
  3. 多个异步任务的聚合,串行,并行等功能

对于 Future 的使用可以参考 [[Future的使用]]

构造异步方法

CompletableFuture 提供了 4 个静态方法来构造一个异步事件

// 含有返回值的异步方法(自定义线程池和默认线程池)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
// 不含有返回值的异步方法(自定义线程池和默认线程池)
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)

supplyAsync 示例

public static void main(String[] args) throws ExecutionException, InterruptedException {
      CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
          System.out.println("当前线程:" + Thread.currentThread().getName());
          return 1;
      });
      System.out.println("线程执行结果:" + future.get());
      System.out.println("主线程:" + Thread.currentThread().getName());
  }
当前线程:ForkJoinPool.commonPool-worker-1
线程执行结果:1
主线程:main

tab runAsync示例

public static void main(String[] args) throws ExecutionException, InterruptedException {
      CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
          System.out.println("当前线程:" + Thread.currentThread().getName());
      });
      System.out.println("线程执行结果:" + future.get());
      System.out.println("主线程:" + Thread.currentThread().getName());
  }
当前线程:ForkJoinPool.commonPool-worker-1
线程执行结果:null
主线程:main

任务与和或的静态方法

CompletableFuture 中还有另外两个特殊的静态方法

// 接收多个CompletableFuture无返回值任务,当所有的CompletableFuture任务执行结束后,返回一个新的CompletableFuture对象
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
//接收多个CompletableFuture带有返回值任务,当任何一个CompletableFuture任务执行完成后,返回一个新的CompletableFuture对象
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);

allOff 代码示例

public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("当前线程:" + Thread.currentThread().getName());
    });
    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("当前线程:" + Thread.currentThread().getName());
    });
    LocalTime now = LocalTime.now();
    System.out.println("before time:" + now.getMinute() + ":" + now.getSecond());
    CompletableFuture.allOf(future1, future2).join();
    now = LocalTime.now();
    System.out.println("after time:" + now.getMinute() + ":" + now.getSecond());
}

执行结果,用了5s才返回,因为要所有都执行完成才返回,就需要看耗时最长的那个线程

before time:50:33
当前线程:ForkJoinPool.commonPool-worker-1
当前线程:ForkJoinPool.commonPool-worker-2
after time:50:38

anyOf()代码示例

public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("当前线程:" + Thread.currentThread().getName());
    });
    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("当前线程:" + Thread.currentThread().getName());
    });
    LocalTime now = LocalTime.now();
    System.out.println("before time:" + now.getMinute() + ":" + now.getSecond());
    CompletableFuture.anyOf(future1, future2).join();
    now = LocalTime.now();
    System.out.println("after time:" + now.getMinute() + ":" + now.getSecond());
}

执行结果,只用了3s就返回了,因为是其中一个执行完成就行,所以看耗时最短的那个线程

before time:53:43
当前线程:ForkJoinPool.commonPool-worker-1
after time:53:46

CompletionStage 详解

  1. CompletionStage 表示任务执行的一个阶段,每个异步任务都会返回一个新的 CompletionStage 对象
  2. 可以针对多个 CompletionStage 对象进行串行、并行或者聚合来进行下一阶段的操作

CompletionStage 类中提供了很多方法来实现多个 CompletionStage 的串行,并行等功能,可以按照如下方式进行分类

flowchart LR
    A[CompletionStage]
    B["纯消费性方法 \n (上一个异步结果 \n 作为当前方法的参数进行计算 \n 都含有 Accept 关键字)"]
        B1[依赖单个 CompletionStage任务完成]
            B11["thenAccept(Consumer)"]
            B12["thenAcceptAsync(Consumer)"]
            B13["thenAcceptAsync(Consumer,Executor)"]
            B1-->B11 & B12 & B13
        B2[依赖两个 CompletionStage任务完成]
            B21["thenAcceptBoth(CompletionStage,Consumer)"]
            B22["thenAcceptBothAsync(CompletionStage,Consumer)"]
            B23["thenAcceptBothAsync(CompletionStage,Consumer,Executor)"]
            B2-->B21 & B22 & B23
        B3[依赖两个 CompletionStage任务中任何一个完成]
            B31["acceptEither(CompletionStage,Consumer)"]
            B32["acceptEitherAsync(CompletionStage,Consumer)"]
            B33["acceptEitherAsync(CompletionStage,Consumer,Executor)"]
            B3-->B31 & B32 & B33
        B-->B1 & B2 & B3

    C["有返回值类型的方法 \n (上一个异步结果 \n 作为当前方法的参数进行计算 \n 并且会产生新的有返回值\n的CompletionStage对象)"]
        C1[依赖单个 CompletionStage任务完成]
            C11["thenApply(Function)"]
            C12["thenApplyAsync(Function)"]
            C13["thenApplyAsync(Function,Executor)"]
            C1-->C11 & C12 & C13
        C2[依赖两个 CompletionStage任务完成]
            C21["thenCombine(CompletionStage,BiFunction)"]
            C22["thenCombineAsync(CompletionStage,BiFunction)"]
            C23["thenCombineAsync(CompletionStage,BiFunction,Executor)"]
            C2-->C21 & C22 & C23
        C3[依赖两个 CompletionStage任务中任何一个完成]
            C31["acceptToEither(CompletionStage,Function)"]
            C32["acceptToEitherAsync(CompletionStage,Function)"]
            C33["acceptToEitherAsync(CompletionStage,Function,Executor)"]
            C3-->C31 & C32 & C33
        C-->C1 & C2 & C3
    D["不消费也没有返回值类型的方法\n不依赖上个阶段的结果\n上一个阶段完成就执行指定的任务\n这类方法都包含 run 关键字"]
        D1[依赖单个 CompletionStage任务完成]
            D11["thenRun(Runnable)"]
            D12["thenRunAsync(Runnable)"]
            D13["thenRunAsync(FuncRunnabletion,Executor)"]
            D1-->D11 & D12 & D13
        D2[依赖两个 CompletionStage任务完成]
            D21["runAfterBoth(CompletionStage,Runnable)"]
            D22["runAfterBothAsync(CompletionStage,Runnable)"]
            D23["runAfterBothAsync(CompletionStage,Runnable,Executor)"]
            D2-->D21 & D22 & D23
        D3[依赖两个 CompletionStage任务中任何一个完成]
            D31["runAfterEither(CompletionStage,Runnable)"]
            D32["runAfterEitherAsync(CompletionStage,Runnable)"]
            D33["runAfterEitherAsync(CompletionStage,Runnable,Executor)"]
            D3-->D31 & D32 & D33
        D-->D1 & D2 & D3
    E[组合类型的方法]
        E1["thenCompose(Function,CompletionStage)"]
        E2["thenComposeAsync(Function,CompletionStage)"]
        E3["thenComposeAsync(Function,CompletionStage,Executor)"]
        E-->E1 & E2 & E3
    A-->B & C & D & E

纯消费型

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("thread main:" + Thread.currentThread().getName());
    CompletableFuture.supplyAsync(()->{
        System.out.println("first stage:" + Thread.currentThread().getName());
        return 1;
    }).thenAccept((Integer v)-> {
        // v 就是第一步返回的结果
        System.out.println("then accept thread:" + Thread.currentThread().getName());
        System.out.println(v + 1); // 2
    }).join();
}
thread main:main
first stage:ForkJoinPool.commonPool-worker-1
then accept thread:main
2

上面代码调用 thenAccept() 使用的是 main 线程,如果是使用 thenAcceptAsync(), 调用结果如下

thread main:main
first stage:ForkJoinPool.commonPool-worker-1
then accept thread:ForkJoinPool.commonPool-worker-1
2

有返回值类型

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("thread main:" + Thread.currentThread().getName());
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        // 第一个阶段任务返回的是 int 类型
        return 1;
    }).thenCombineAsync(CompletableFuture.supplyAsync(() -> {
        // 第二个阶段任务返回的是 String 类型
        return "2";
    }), (Integer i1, String s1) -> {
        return i1 + s1;
    });
    System.out.println(completableFuture.get()); // 12
}

不消费也没有返回值类型

public static void main(String[] args) throws Exception {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    CompletableFuture.supplyAsync(() -> {
        try {
            // 第一个阶段任务等待4s
            Thread.sleep(4000);
            System.out.println("第一个阶段:" + LocalDateTime.now().format(formatter));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return 1;
    }).runAfterEitherAsync(CompletableFuture.supplyAsync(() -> {
        // 第二个阶段任务等待2s
        try {
            Thread.sleep(2000);
            System.out.println("第二个阶段:" + LocalDateTime.now().format(formatter));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "2";
    }), () -> {
        System.out.println("最终:" + LocalDateTime.now().format(formatter));
        System.out.println("最终执行的任务");
    }).join();
}

执行结果

第二个阶段:2023-11-20 23:56:32
最终:2023-11-20 23:56:32
最终执行的任务

组合类型

  1. thenCompose()是多任务组合方法,它的作用是把两个CompletionStage任务进行组合达到串行执行的目的,也就是把第一个任务的执行结果作为参数传递给第二个任务执行,它有点类似于前面提到的thenCombine()方法,最大的不同在于thenCompose()方法中的任务存在先后关系,而thenCombine()中两个任务是并行执行的

异常处理

CompletionStage 是链式处理,当前面的任务出现异常的时候,会导致后面的任务无法处理

public static void main(String[] args) throws Exception {
    CompletableFuture.supplyAsync(() -> {
        throw new RuntimeException("第一个任务出现异常");
    }).thenRun(()->{
        // 这里并不会打印,因为前面一个任务出现了异常,导致该任务无法执行
        System.out.println("第二个任务");
    }).join();
}

所以在 CompletionStage 中也提供了异常处理的相关方法,主要有以下三类

flowchart LR
    A[CompletionStage异常处理]
    B["以whenComplete前缀开头的方法\n不论前置的CompletionStage任务是正常执行结束\n还是出现异常,都能够触发特定的action\n这些方法都接收两个参数,\n一个是正常的结果,一个是异常\n没有异常时第二个就是null"]
        B1["whenComplete(BiConsumer)"]
        B2["whenCompleteAsync(BiConsumer)"]
        B3["whenCompleteAsync(BiConsumer,Executor)"]
        B-->B1 & B2 & B3
    C["以handle前缀开头的方法\n表示前置任务执行完成后,\n不管前置任务执行状态是正常还是异常,\n都会执行其中的函数fn,\n它和whenComplete类方法的\n作用几乎一致,不同点在于,\n这类方法是有返回值类型的方法"]
        C1["handle(BiFunction)"]
        C2["handleAsync(BiFunction)"]
        C3["handleAsync(BiFunction,Executor)"]
        C-->C1 & C2 & C3
    D["exceptionally()"]
        D1["exceptionally()方法接收一个函数fn,\n当上一个CompletionStage出现异常时,\n会把该异常作为参数传递给函数fn。\n该方法有一个CompletionStage的返回值,\n说明当前方法可以在接收到\n上一个阶段的异常时进行进一步处理,\n返回一个新的CompletionStage对象实例"]
        D-->D1
    A-->B & C & D

whenComplete类型方法

没有异常的情况

public static void main(String[] args) throws Exception {
    CompletableFuture.supplyAsync(() -> {
        return 1;
    }).whenCompleteAsync((result, excep)->{
        System.out.println("任务结果:" + result);
        System.out.println("第一个任务的异常:" + excep);
    }).join();
}

执行结果

任务结果:1
第一个任务的异常:null

有异常的情况

public static void main(String[] args) throws Exception {
    CompletableFuture.supplyAsync(() -> {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new RuntimeException("第一个任务出现异常");
        }
        return 1;
    }).whenCompleteAsync((result, excep)->{
        System.out.println("任务结果:" + result);
        System.out.println("第一个任务的异常:" + excep);
    }).join();
}

执行结果

任务结果:null
第一个任务的异常:java.util.concurrent.CompletionException: java.lang.RuntimeException: 第一个任务出现异常
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: 第一个任务出现异常

handle 类型方法

public static void main(String[] args) throws Exception {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new RuntimeException("第一个任务出现异常");
        }
        return 1;
    }).handle((result, excep) -> {
        System.out.println("任务结果:" + result);
        System.out.println("第一个任务的异常:" + excep.toString().substring(1, 70));
        return "第二个任务的结果";
    });
    // 最终的执行结果
    System.out.println(completableFuture.get());
}

执行结果

任务结果:null
第一个任务的异常:ava.util.concurrent.CompletionException: java.lang.RuntimeException: 
第二个任务的结果

exceptionally()方法

public static void main(String[] args) throws Exception {
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new RuntimeException("第一个任务出现异常");
        }
        return 1;
    }).exceptionally(exec -> {
        // exceptionally 里面的返回值需要和前面的 stage 返回值保持一致
        return Integer.valueOf("2");
    });
    // 最终的执行结果
    System.out.println(completableFuture.get());
}