zoukankan      html  css  js  c++  java
  • CompletableFuture 使用介绍

    来自:https://www.cnblogs.com/hansc-blog/p/10645748.html

    一、几个 static 方法,它们使用任务来实例化一个 CompletableFuture 实例。

    上面的代码确实没什么用,下面介绍几个 static 方法,它们使用任务来实例化一个 CompletableFuture 实例。

    CompletableFuture.runAsync(Runnable runnable);
    CompletableFuture.runAsync(Runnable runnable, Executor executor);

    CompletableFuture.supplyAsync(Supplier supplier);
    CompletableFuture.supplyAsync(Supplier supplier, Executor executor)
    runAsync 方法接收的是 Runnable 的实例,意味着它没有返回值
    supplyAsync 方法对应的是有返回值的情况
    这两个方法的带 executor 的变种,表示让任务在指定的线程池中执行,不指定的话,通常任务是在 ForkJoinPool.commonPool() 线程池中执行的。
    好的,现在我们已经有了第一个 CompletableFuture 实例了,我们来看接下来的内容

    二、任务执行顺序

    我们先来看执行两个任务的情况,首先执行任务 A,然后将任务 A 的结果传递给任务 B。

    其实这里有很多种情况,任务 A 是否有返回值,任务 B 是否需要任务 A 的返回值,任务 B 是否有返回值,等等。有个明确的就是,肯定是任务 A 执行完后再执行任务 B。

    我们用下面的 6 行代码来说:

    CompletableFuture.runAsync(() -> {}).thenRun(() -> {});
    CompletableFuture.runAsync(() -> {}).thenAccept(resultA -> {});
    CompletableFuture.runAsync(() -> {}).thenApply(resultA -> "resultB");

    CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {});
    CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {});
    CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB");
    前面 3 行代码演示的是,任务 A 无返回值,所以对应的,第 2 行和第 3 行代码中,resultA 其实是 null。

    第 4 行用的是 thenRun(Runnable runnable),任务 A 执行完执行 B,并且 B 不需要 A 的结果。

    第 5 行用的是 thenAccept(Consumer action),任务 A 执行完执行 B,B 需要 A 的结果,但是任务 B 不返回值。

    第 6 行用的是 thenApply(Function fn),任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值。

    这一小节说完了,如果任务 B 后面还有任务 C,往下继续调用 .thenXxx() 即可。

    三、异常处理

    说到这里,我们顺便来说下 CompletableFuture 的异常处理。这里我们要介绍两个方法:

    public CompletableFuture exceptionally(Function<Throwable, ? extends T> fn);
    public CompletionStage handle(BiFunction<? super T, Throwable, ? extends U> fn);
    看下面的代码:

    CompletableFuture.supplyAsync(() -> "resultA")
    .thenApply(resultA -> resultA + " resultB")
    .thenApply(resultB -> resultB + " resultC")
    .thenApply(resultC -> resultC + " resultD");
    上面的代码中,任务 A、B、C、D 依次执行,如果任务 A 抛出异常(当然上面的代码不会抛出异常),那么后面的任务都得不到执行。如果任务 C 抛出异常,那么任务 D 得不到执行。

    那么我们怎么处理异常呢?看下面的代码,我们在任务 A 中抛出异常,并对其进行处理:

    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException();
    })
    .exceptionally(ex -> "errorResultA")
    .thenApply(resultA -> resultA + " resultB")
    .thenApply(resultB -> resultB + " resultC")
    .thenApply(resultC -> resultC + " resultD");

    System.out.println(future.join());
    上面的代码中,任务 A 抛出异常,然后通过 .exceptionally() 方法处理了异常,并返回新的结果,这个新的结果将传递给任务 B。所以最终的输出结果是:

    errorResultA resultB resultC resultD
    再看下面的代码,我们来看下另一种处理方式,使用 handle(BiFunction fn) 来处理异常:

    CompletableFuture future = CompletableFuture.supplyAsync(() -> "resultA")
    .thenApply(resultA -> resultA + " resultB")
    // 任务 C 抛出异常
    .thenApply(resultB -> {throw new RuntimeException();})
    // 处理任务 C 的返回值或异常
    .handle(new BiFunction<Object, Throwable, Object>() {
    @Override
    public Object apply(Object re, Throwable throwable) {
    if (throwable != null) {
    return "errorResultC";
    }
    return re;
    }
    })
    .thenApply(resultC -> resultC + " resultD");

    System.out.println(future.join());
    上面的代码使用了 handle 方法来处理任务 C 的执行结果,上面的代码中,re 和 throwable 必然有一个是 null,它们分别代表正常的执行结果和异常的情况。

    当然,它们也可以都为 null,因为如果它作用的那个 CompletableFuture 实例没有返回值的时候,re 就是 null。

    四、取消任务结果

    上面一节,我们说的是,任务 A 执行完 -> 任务 B 执行完 -> 执行任务 C,它们之间有先后执行关系,因为后面的任务依赖于前面的任务的结果。

    这节我们来看怎么让任务 A 和任务 B 同时执行,然后取它们的结果进行后续操作。这里强调的是任务之间的并行工作,没有先后执行顺序。

    如果使用 Future 的话,我们通常是这么写的:

    ExecutorService executorService = Executors.newCachedThreadPool();

    Future futureA = executorService.submit(() -> "resultA");
    Future futureB = executorService.submit(() -> "resultB");

    String resultA = futureA.get();
    String resultB = futureB.get();
    接下来,我们看看 CompletableFuture 中是怎么写的,看下面的几行代码:

    CompletableFuture cfA = CompletableFuture.supplyAsync(() -> "resultA");
    CompletableFuture cfB = CompletableFuture.supplyAsync(() -> "resultB");

    cfA.thenAcceptBoth(cfB, (resultA, resultB) -> {});
    cfA.thenCombine(cfB, (resultA, resultB) -> "result A + B");
    cfA.runAfterBoth(cfB, () -> {});
    第 3 行代码和第 4 行代码演示了怎么使用两个任务的结果 resultA 和 resultB,它们的区别在于,thenAcceptBoth 表示后续的处理不需要返回值,而 thenCombine 表示需要返回值。

    如果你不需要 resultA 和 resultB,那么还可以使用第 5 行描述的 runAfterBoth 方法。

    注意,上面的写法和下面的写法是没有区别的:

    CompletableFuture cfA = CompletableFuture.supplyAsync(() -> "resultA");

    cfA.thenAcceptBoth(CompletableFuture.supplyAsync(() -> "resultB"),
    (resultA, resultB) -> {});
    千万不要以为这种写法任务 A 执行完了以后再执行任务 B。

    五、领取多个任务

    接下来,我们将介绍两个非常简单的静态方法:allOf() 和 anyOf() 方法。

    public static CompletableFuture allOf(CompletableFuture<?>... cfs){...}
    public static CompletableFuture

  • 相关阅读:
    Sharding-JDBC多数据源动态切换
    U 盘安装 CentOS 7 时出现 No Caching mode page found 问题的解决
    sudo 密码直接添加到命令行以方便实现脚本自动化
    Python3 Windows 虚拟环境的若干问题
    20 张图让你彻底弄懂 HTTPS 原理!
    全网写得最好的分库分表之 Sharding-JDBC 中间件介绍
    以为线程池很简单,结果第一道题就被干趴下了!
    以为线程池很简单,没想到第一问就被干趴下了
    分布式事务,看这篇就够了!
    我是一个线程池
  • 原文地址:https://www.cnblogs.com/dreamroute/p/13706784.html
Copyright © 2011-2022 走看看