参考资料: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

 

posted on 2021-12-25 22:53  kuotian  阅读(102)  评论(0编辑  收藏  举报