• CompletableFuture学习笔记


    参考资料:https://gitee.com/phui/share-concurrent,https://www.jianshu.com/p/6bac52527ca4

    一、CompletableFuture介绍

    使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

    从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

    二、基础用法

     1 public class SmallTool {
     2     public static void sleepMillis(long millis) {
     3         try {
     4             Thread.sleep(millis);
     5         } catch (InterruptedException e) {
     6             e.printStackTrace();
     7         }
     8     }
     9 
    10     public static void printTimeAndThread(String tag) {
    11         String result = new StringJoiner("\t|\t")
    12                 .add(String.valueOf(System.currentTimeMillis()))
    13                 .add(String.valueOf(Thread.currentThread().getId()))
    14                 .add(Thread.currentThread().getName())
    15                 .add(tag)
    16                 .toString();
    17         System.out.println(result);
    18     }
    19 }
    工具

    2.1 supplyAsync(开启异步任务)

     1 public class _01_supplyAsync {
     2     public static void main(String[] args) {
     3         SmallTool.printTimeAndThread("小白进入餐厅");
     4         SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
     5 
     6         CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
     7             SmallTool.printTimeAndThread("厨师炒菜");
     8             SmallTool.sleepMillis(200);
     9             SmallTool.printTimeAndThread("厨师打饭");
    10             SmallTool.sleepMillis(100);
    11             return "番茄炒蛋 + 米饭 做好了";
    12         });
    13 
    14         SmallTool.printTimeAndThread("小白在打王者");
    15         SmallTool.printTimeAndThread(String.format("%s ,小白开吃", cf1.join()));
    16     }
    17 }
    supplyAsync

    执行结果

    Fork/Join框架介绍 )

    1640426058812    |    1    |    main    |    小白进入餐厅
    1640426058812    |    1    |    main    |    小白点了 番茄炒蛋 + 一碗米饭
    1640426058849    |    1    |    main    |    小白在打王者
    1640426058850    |    11    |    ForkJoinPool.commonPool-worker-9    |    厨师炒菜
    1640426059059    |    11    |    ForkJoinPool.commonPool-worker-9    |    厨师打饭
    1640426059187    |    1    |    main    |    番茄炒蛋 + 米饭 做好了 ,小白开吃
    result

    2.2 thenCompose(连接异步任务)

    thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。

     1 public class _02_thenCompose {
     2     public static void main(String[] args) {
     3         SmallTool.printTimeAndThread("小白进入餐厅");
     4         SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
     5 
     6         CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
     7             SmallTool.printTimeAndThread("厨师炒菜");
     8             SmallTool.sleepMillis(200);
     9             return "番茄炒蛋";
    10         }).thenCompose(dish -> CompletableFuture.supplyAsync(() -> {
    11             SmallTool.printTimeAndThread("服务员打饭");
    12             SmallTool.sleepMillis(100);
    13             return dish + " + 米饭";
    14         }));
    15 
    16         SmallTool.printTimeAndThread("小白在打王者");
    17         SmallTool.printTimeAndThread(String.format("%s 好了,小白开吃", cf1.join()));
    18     }
    19 }
    thenCompose

    执行结果

    1640426372421    |    1    |    main    |    小白进入餐厅
    1640426372421    |    1    |    main    |    小白点了 番茄炒蛋 + 一碗米饭
    1640426372461    |    11    |    ForkJoinPool.commonPool-worker-9    |    厨师炒菜
    1640426372463    |    1    |    main    |    小白在打王者
    1640426372673    |    12    |    ForkJoinPool.commonPool-worker-2    |    服务员打饭
    1640426372803    |    1    |    main    |    番茄炒蛋 + 米饭 好了,小白开吃
    result

    2.3 thenCombine(合并异步任务)

    thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

     1 public class _03_thenCombine {
     2     public static void main(String[] args) {
     3         SmallTool.printTimeAndThread("小白进入餐厅");
     4         SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
     5 
     6         CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
     7             SmallTool.printTimeAndThread("厨师炒菜");
     8             SmallTool.sleepMillis(200);
     9             return "番茄炒蛋";
    10         }).thenCombine(CompletableFuture.supplyAsync(() -> {
    11             SmallTool.printTimeAndThread("服务员蒸饭");
    12             SmallTool.sleepMillis(300);
    13             return "米饭";
    14         }), (dish, rice) -> {
    15             SmallTool.printTimeAndThread("服务员打饭");
    16             SmallTool.sleepMillis(100);
    17             return String.format("%s + %s 好了", dish, rice);
    18         });
    19 
    20         SmallTool.printTimeAndThread("小白在打王者");
    21         SmallTool.printTimeAndThread(String.format("%s ,小白开吃", cf1.join()));
    22     }
    23 
    24     /**
    25      * 用 applyAsync 也能实现
    26      */
    27     private static void applyAsync() {
    28         SmallTool.printTimeAndThread("小白进入餐厅");
    29         SmallTool.printTimeAndThread("小白点了 番茄炒蛋 + 一碗米饭");
    30 
    31         CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
    32             SmallTool.printTimeAndThread("厨师炒菜");
    33             SmallTool.sleepMillis(200);
    34             return "番茄炒蛋";
    35         });
    36         CompletableFuture<String> race = CompletableFuture.supplyAsync(() -> {
    37             SmallTool.printTimeAndThread("服务员蒸饭");
    38             SmallTool.sleepMillis(300);
    39             return "米饭";
    40         });
    41         SmallTool.printTimeAndThread("小白在打王者");
    42 
    43         String result = String.format("%s + %s 好了", cf1.join(), race.join());
    44         SmallTool.printTimeAndThread("服务员打饭");
    45         SmallTool.sleepMillis(100);
    46 
    47         SmallTool.printTimeAndThread(String.format("%s ,小白开吃", result));
    48     }
    49 }
    thenCombine

    执行结果

    1640426656536    |    1    |    main    |    小白进入餐厅
    1640426656537    |    1    |    main    |    小白点了 番茄炒蛋 + 一碗米饭
    1640426656581    |    11    |    ForkJoinPool.commonPool-worker-9    |    厨师炒菜
    1640426656582    |    12    |    ForkJoinPool.commonPool-worker-2    |    服务员蒸饭
    1640426656586    |    1    |    main    |    小白在打王者
    1640426656883    |    12    |    ForkJoinPool.commonPool-worker-2    |    服务员打饭
    1640426657033    |    1    |    main    |    番茄炒蛋 + 米饭 好了 ,小白开吃
    result

    2.4 thenApply(任务的后置处理)

    当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。

    thenApply是执行当前任务的线程执行继续执行 thenApply的任务。

    thenApplyAsync是把 thenApplyAsync这个任务继续提交给线程池来进行执行。

    public class _01_thenApply {
        public static void main(String[] args) {
            SmallTool.printTimeAndThread("小白吃好了");
            SmallTool.printTimeAndThread("小白 结账、要求开发票");
    
            CompletableFuture<String> invoice = CompletableFuture.supplyAsync(() -> {
                SmallTool.printTimeAndThread("服务员收款 500元");
                SmallTool.sleepMillis(100);
                return "500";
            }).thenApplyAsync(money -> {
                SmallTool.printTimeAndThread(String.format("服务员开发票 面额 %s元", money));
                SmallTool.sleepMillis(200);
                return String.format("%s元发票", money);
            });
    
            SmallTool.printTimeAndThread("小白 接到朋友的电话,想一起打游戏");
    
            SmallTool.printTimeAndThread(String.format("小白拿到%s,准备回家", invoice.join()));
        }
    }
    thenApply

    执行结果

    1640427311931    |    1    |    main    |    小白吃好了
    1640427311932    |    1    |    main    |    小白 结账、要求开发票
    1640427311974    |    11    |    ForkJoinPool.commonPool-worker-9    |    服务员收款 500元
    1640427311974    |    1    |    main    |    小白 接到朋友的电话,想一起打游戏
    1640427312100    |    11    |    ForkJoinPool.commonPool-worker-9    |    服务员开发票 面额 500元
    1640427312307    |    1    |    main    |    小白拿到500元发票,准备回家
    result

    2.5 applyToEither(获取最先完成的任务)

    两个CompletionStage,返回执行快的结果,用快的CompletionStage的结果进行下一步的转化操作。

     1 public class _02_applyToEither {
     2     public static void main(String[] args) {
     3         SmallTool.printTimeAndThread("小白走出餐厅,来到公交站");
     4         SmallTool.printTimeAndThread("等待 700路 或者 800路 公交到来");
     5 
     6         CompletableFuture<String> bus = CompletableFuture.supplyAsync(() -> {
     7             SmallTool.printTimeAndThread("700路公交正在赶来");
     8             SmallTool.sleepMillis(100);
     9             return "700路到了";
    10         }).applyToEither(CompletableFuture.supplyAsync(() -> {
    11             SmallTool.printTimeAndThread("800路公交正在赶来");
    12             SmallTool.sleepMillis(200);
    13             return "800路到了";
    14         }), firstComeBus -> firstComeBus);
    15 
    16         SmallTool.printTimeAndThread(String.format("%s,小白坐车回家", bus.join()));
    17     }
    18 }
    applyToEither

    执行结果

    1640427683299    |    1    |    main    |    小白走出餐厅,来到公交站
    1640427683300    |    1    |    main    |    等待 700路 或者 800路 公交到来
    1640427683363    |    11    |    ForkJoinPool.commonPool-worker-9    |    700路公交正在赶来
    1640427683363    |    12    |    ForkJoinPool.commonPool-worker-2    |    800路公交正在赶来
    1640427683498    |    1    |    main    |    700路到了,小白坐车回家
    result

    2.6 exceptionally(异常处理)

    当CompletableFuture的抛出异常的时候,可以执行特定的Action。

     1 public class _03_exceptionally {
     2     public static void main(String[] args) {
     3         SmallTool.printTimeAndThread("张三走出餐厅,来到公交站");
     4         SmallTool.printTimeAndThread("等待 700路 或者 800路 公交到来");
     5 
     6         CompletableFuture<String> bus = CompletableFuture.supplyAsync(() -> {
     7             SmallTool.printTimeAndThread("700路公交正在赶来");
     8             SmallTool.sleepMillis(100);
     9             return "700路到了";
    10         }).applyToEither(CompletableFuture.supplyAsync(() -> {
    11             SmallTool.printTimeAndThread("800路公交正在赶来");
    12             SmallTool.sleepMillis(200);
    13             return "800路到了";
    14         }), firstComeBus -> {
    15             SmallTool.printTimeAndThread(firstComeBus);
    16             if (firstComeBus.startsWith("700")) {
    17                 throw new RuntimeException("撞树了……");
    18             }
    19             return firstComeBus;
    20         }).exceptionally(e -> {
    21             SmallTool.printTimeAndThread(e.getMessage());
    22             SmallTool.printTimeAndThread("小白叫出租车");
    23             return "出租车 叫到了";
    24         });
    25 
    26         SmallTool.printTimeAndThread(String.format("%s,小白坐车回家", bus.join()));
    27     }
    28 }
    exceptionally

    三、API使用详解

    3.1 runAsync 和 supplyAsync(开启异步任务)

    CompletableFuture 提供了四个静态方法来创建一个异步操作。

    • runAsync方法不提供返回值。

    • supplyAsync提供返回值。

    没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。

    public static CompletableFuture<Void> runAsync(Runnable runnable)
    public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

    3.2 thenCompose(连接异步任务)

    thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。

    public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
    public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn)
    public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor)

    thenCompose前面和后面的两段代码是两个任务(厨师、服务员B)。区别如下:

    • thenCompose :是执行当前任务的线程执行继续执行。(效果如厨师和服务员A在同一个任务。)

    • thenComposeAsync:是把 thenComposeAsync这个任务继续提交给线程池来进行执行。厨师和服务员A在两个任务

     1 public class _02_thenCompose {
     2     public static void main(String[] args) {
     3         CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
     4             SmallTool.printTimeAndThread("厨师炒菜");
     5             SmallTool.sleepMillis(200);
     6             return "番茄炒蛋";
     7         }).thenCompose(dish -> {
     8             SmallTool.printTimeAndThread("服务员A 准备打饭,但是被领导叫走,打饭交接给服务员B");
     9 
    10             return CompletableFuture.supplyAsync(() -> {
    11                 SmallTool.printTimeAndThread("服务员B 打饭");
    12                 SmallTool.sleepMillis(100);
    13                 return dish + " + 米饭";
    14             });
    15         });
    16 
    17         SmallTool.printTimeAndThread(cf1.join()+"好了,开饭");
    18     }
    19 }
    example

    thenCompose执行结果

    1640438004703    |    12    |    ForkJoinPool.commonPool-worker-9    |    厨师炒菜
    1640438006715    |    12    |    ForkJoinPool.commonPool-worker-9    |    服务员A 准备打饭,但是被领导叫走,打饭交接给服务员B
    1640438006717    |    13    |    ForkJoinPool.commonPool-worker-2    |    服务员B 打饭
    1640438006827    |    1    |    main    |    番茄炒蛋 + 米饭好了,开饭
    thenCompose执行结果

    thenComposeAsync执行结果

    1640438345289    |    12    |    ForkJoinPool.commonPool-worker-9    |    厨师炒菜
    1640438347301    |    13    |    ForkJoinPool.commonPool-worker-2    |    服务员A 准备打饭,但是被领导叫走,打饭交接给服务员B
    1640438347304    |    12    |    ForkJoinPool.commonPool-worker-9    |    服务员B 打饭
    1640438347410    |    1    |    main    |    番茄炒蛋 + 米饭好了,开饭
    thenComposeAsync执行结果

    3.3 thenCombine(合并异步任务)、thenAcceptBoth、runAfterBoth

    thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

    • thenCombine 可以得到前面两个任务的结果,经处理,返回一个结果

    • thenAcceptBoth可以得到前面两个任务的结果,经处理,无返回结果

    • runAfterBoth不关心前面两个任务的结果,经处理,无返回值

    public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
    public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
    public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);
    
    public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
    public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);
    
    public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
    public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

    3.4 thenApply(任务的后置处理)、thenAccept、thenRun

    当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。

    thenApply是执行当前任务的线程执行继续执行。作用类似于把后面的代码块放到上一个任务的末尾。会交给一个线程去运行。

    thenApplyAsync是把 thenApplyAsync这个任务继续提交给线程池来进行执行。将两部分代码当做两个独立任务。在第二个任务开始前,需要把第一个任务处理完,将第一个任务的结果交给第二个任务。

    • thenApply会接受上个任务的结果参数,经处理,返回结果

    • thenAccept会接受上个任务的结果参数,经处理,无返回值

    • thenRun不关心上个任务的结果参数,经处理,无返回值

    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)
        
    public CompletionStage<Void> thenAccept(Consumer<? super T> action);
    public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
    public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
    
    public CompletionStage<Void> thenRun(Runnable action);
    public CompletionStage<Void> thenRunAsync(Runnable action);
    public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

    Function<? super T,? extends U> T:上一个任务返回结果的类型 U:当前任务的返回值类型

    3.5 applyToEither(获取最先完成的任务)、acceptEither 、runAfterEither

    两个CompletionStage,返回执行快的结果,用快的CompletionStage的结果进行下一步的转化操作。

    • applyToEither获取前两个任务中最先执行完的任务结果,经处理,返回一个结果

    • acceptEither获取前两个任务中最先执行完的任务结果,经处理,无返回值

    • runAfterEither不关心前两个任务中最先执行完的任务结果,经处理,无返回值

    public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
    public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
    
    public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
    public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
    public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);
    
    public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
    public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
    public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);

    3.6 exceptionally(异常处理)、handle、whenComplete

    当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。

    • exceptionally处理前面任务的异常,并把异常修正为正常值

    • handle前面的程序发生异常,那么就接收到异常;前面的结果正常则正常处理。handle会返回一个结果,让后续方法正常执行。(handle 是执行任务完成时对结果的处理。handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。)

    • whenComplete与handle相同,但无返回值。

    public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
        
    public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
    public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
    public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
    
    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)

    Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。

    whenComplete 和 whenCompleteAsync 的区别: whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。 whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。

     

    四、参考

    https://www.jianshu.com/p/6bac52527ca4

    https://gitee.com/phui/share-concurrent

     

  • 相关阅读:
    一个很好用的linux下系统清理工具
    怎样将linux+qt在1S中内启动的幻灯片教程
    通过 ulimit 改善系统性能
    UBI文件系统
    利用BLCR加快Android的启动过程
    工作队列中的sleep导致控制台无法输入问题
    android system setup and building (3)
    物理地址和虚拟地址1 (MMU)
    对 makefile 中 eval 函数的学习体会
    location.href语句与火狐不兼容的问题
  • 原文地址:https://www.cnblogs.com/kuotian/p/15731893.html
Copyright © 2020-2023  润新知