参考资料: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 }
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 }
执行结果
1640426058812 | 1 | main | 小白进入餐厅 1640426058812 | 1 | main | 小白点了 番茄炒蛋 + 一碗米饭 1640426058849 | 1 | main | 小白在打王者 1640426058850 | 11 | ForkJoinPool.commonPool-worker-9 | 厨师炒菜 1640426059059 | 11 | ForkJoinPool.commonPool-worker-9 | 厨师打饭 1640426059187 | 1 | main | 番茄炒蛋 + 米饭 做好了 ,小白开吃
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 }
执行结果
1640426372421 | 1 | main | 小白进入餐厅 1640426372421 | 1 | main | 小白点了 番茄炒蛋 + 一碗米饭 1640426372461 | 11 | ForkJoinPool.commonPool-worker-9 | 厨师炒菜 1640426372463 | 1 | main | 小白在打王者 1640426372673 | 12 | ForkJoinPool.commonPool-worker-2 | 服务员打饭 1640426372803 | 1 | main | 番茄炒蛋 + 米饭 好了,小白开吃
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 }
执行结果
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 | 番茄炒蛋 + 米饭 好了 ,小白开吃
2.4 thenApply(任务的后置处理)
当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。
thenApply是执行当前任务的线程执行继续执行 thenApply的任务。
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())); } }
执行结果
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元发票,准备回家
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 }
执行结果
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路到了,小白坐车回家
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 }
三、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在同一个任务。)
-
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 }
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 | 番茄炒蛋 + 米饭好了,开饭
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 | 番茄炒蛋 + 米饭好了,开饭
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:上一个任务返回结果的类型
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