JUC并发编程进阶2:CompletableFuture

1 Future接口理论知识复习

Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等

举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)

一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂任务
在这里插入图片描述

2 Future接口常用实现类FutureTask异步任务

2.1 Future接口能干什么

  • Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果

2.2 Future接口相关架构

  • 目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
  • 代码实现:Runnable接口+Callable接口+Future接口和FutureTask实现类。

在这里插入图片描述

/*** @package: com.yunyang.bilibili.juc.completablefeture* @description: FutureTask开启异步任务* @author: Yunyang* @date: 2024/10/12  23:15* @version:1.0**/
public class CompletableFutureDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<String> futureTask = new FutureTask<>(new MyThread());Thread t1 = new Thread(futureTask,"t1");//开启一个异步线程t1.start();//有返回hello CallableSystem.out.println(futureTask.get());}
}class MyThread implements Callable<String>{@Overridepublic String call() throws Exception {System.out.println("----come in call() ");return "hello Callable";}
}

2.3 Future编码实战和优缺点分析

  • 优点:Future+线程池异步多线程任务配合,能显著提高程序的运行效率

  • 缺点

    • get()阻塞:一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞
    • isDone()轮询:轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞
  • 结论
    Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果

/*** @package: com.yunyang.bilibili.juc.completablefeture* @description:Future获取结果get和轮询* @author: Yunyang* @date: 2024/10/15  21:28* @version:1.0**/
public class FutureAPIDemo {public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {FutureTask<String> futureTask = new FutureTask<>(() -> {System.out.println(Thread.currentThread().getName() + "\t-----come in");try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}return "task over";});Thread t1 = new Thread(futureTask, "t1");t1.start();System.out.println(Thread.currentThread().getName() + "\t----忙其他任务了");//这样会有阻塞的可能,在程序没有计算完毕的情况下// System.out.println(futureTask.get());//只愿意等待三秒,计算未完成直接抛出异常// System.out.println(futureTask.get(3, TimeUnit.SECONDS));// 轮询while (true){if(futureTask.isDone()){System.out.println(futureTask.get());break;} else {TimeUnit.MICROSECONDS.sleep(500);System.out.println("正在处理中,不要催了,越催越慢");}}}
}

2.4 完成一些复杂的任务

  • 对于简单的业务场景使用Future完全ok

  • 回调通知

    • 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
    • 通过轮询的方式去判断任务是否完成这样非常占cpu并且代码也不优雅
  • 创建异步任务:Future+线程池组合

  • 多个任务前后依赖可以组合处理(水煮鱼—>买鱼—>调料—>下锅):

    • 想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要钱一个异步任务的值
    • 想将两个或多个异步计算font color=red>合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果
  • 对计算速度选最快的:

    • 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果
  • 结论:

    • 使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求
    • 从i到i++
    • Future能干的,CompletableFuture都能干

3 CompletableFutureFuture的改进

3.1 CompletableFuture为什么会出现

  • get()方法在Future计算完成之前会一直处在阻塞状态下,阻塞的方式和异步编程的设计理念相违背
  • isDene()方法容易耗费cpu资源(cpu空转),
  • 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果

JDK8设计出CompletableFuture,CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。

3.2 CompletableFutureCompletionStage介绍

3.2.1 类架构说明

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {

在这里插入图片描述

3.2.2 接口CompletionStage

  • 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
  • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
  • 一个阶段的计算执行可以是一个Function, Consumer或者Runnable。比如:
stage.thenApply(x -> square(x).thenAccept(x -> System.out.print(x)).thenRun(()->system.out.println())

3.2.3 类CompletableFuture

  • 提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
  • 它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作
  • 实现了Future和CompletionStage接口

3.3 核心的四个静态方法,来创建一个异步任务

CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,提供了多种方法来创建和管理异步任务。以下是四个核心的静态方法,用于创建不同类型的异步任务:

3.3.1 runAsync - 无返回值

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
  • 用途:执行一个没有返回值的异步任务。
  • 参数
    • runnable:要执行的任务。
    • executor:可选的线程池,如果不指定,默认使用 ForkJoinPool.commonPool()

3.3.2 supplyAsync - 有返回值

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
  • 用途:执行一个有返回值的异步任务。
  • 参数
    • supplier:提供返回值的任务。
    • executor:可选的线程池,如果不指定,默认使用 ForkJoinPool.commonPool()

3.3.3 Executor 参数说明

  • 默认线程池:如果没有指定 Executor,默认使用 ForkJoinPool.commonPool() 作为线程池执行异步代码。
  • 自定义线程池:如果指定了线程池,则使用自定义的线程池执行异步代码。

3.3.4 CompletableFuture 减少阻塞和轮询

  • 回调机制CompletableFuture 支持传入回调对象,当异步任务完成或发生异常时,自动调用回调对象的方法,减少阻塞和轮询。

3.3.5 CompletableFuture 的优点

  • 自动回调:异步任务结束时,自动回调某个对象的方法。
  • 顺序执行:主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行。
  • 异常处理:异步任务出错时,自动回调某个对象的方法。

3.3.6 示例代码

  • 四个静态方法的演示
/*** @package: com.yunyang.bilibili.juc.completablefeture* @description: 四个静态方法的演示* @author: Yunyang* @date: 2024/10/16  21:41* @version:1.0**/
public class CompletableFutureBuildDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService threadPool = Executors.newFixedThreadPool(3);CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {System.out.println(Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}},threadPool);System.out.println(completableFuture.get()); // 输出:nullCompletableFuture<String> supplyAsyncCompletableFuture = CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}return "hello supplyAsync";},threadPool);System.out.println(supplyAsyncCompletableFuture.get()); // 输出:hello supplyAsyncthreadPool.shutdown();}
}

分析:

  1. 创建线程池:使用 Executors.newFixedThreadPool(3) 创建一个固定大小的线程池。
  2. 无返回值的异步任务:使用 runAsync 方法执行一个无返回值的异步任务。
  3. 有返回值的异步任务:使用 supplyAsync 方法执行一个有返回值的异步任务。
  4. 处理异步任务的结果:使用 get() 方法处理异步任务的返回值。
  5. 关闭线程池:在任务完成后关闭线程池。
  • CompletableFuture减少阻塞和轮询
/*** @package: com.yunyang.bilibili.juc.completablefeture* @description: CompletableFuture减少阻塞和轮询* @author: Yunyang* @date: 2024/10/16  21:53* @version:1.0**/
public class CompletableFutureUserDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService threadPool = Executors.newFixedThreadPool(3);try {CompletableFuture.supplyAsync(()->{System.out.println(Thread.currentThread().getName() + "----come in");final int result =ThreadLocalRandom.current().nextInt(10);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("-----1秒钟后出结果:" + result);if(result > 5){int i = 1/0; //模拟产生异常情况}return result;},threadPool).whenComplete((v,e)->{if(e == null){System.out.println("-------计算完成,更新系统UpdateValue:" + v);}}).exceptionally(e->{e.printStackTrace();System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());return null;});System.out.println(Thread.currentThread().getName() + "线程先去忙其他任务");} catch (Exception e) {throw new RuntimeException(e);} finally {threadPool.shutdown();}// 主线程不要立刻结束,否则completableFuture默认使用的线程池会立刻关闭:暂停3秒钟线程
/*        try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {throw new RuntimeException(e);}*/}private static void future1() throws InterruptedException, ExecutionException {CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName() + "----come in");int result = ThreadLocalRandom.current().nextInt(10);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("----1秒钟后出结果: " + result);return result;});System.out.println(Thread.currentThread().getName() + "线程先去忙其他任务");System.out.println(completableFuture.get());}
}/*** 有异常情况:* pool-1-thread-1----come in* main线程先去忙其他任务* -----1秒钟后出结果:7* java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero* 异常情况:java.lang.ArithmeticException: / by zero	java.lang.ArithmeticException: / by zero*//*** 无异常情况:* pool-1-thread-1----come in* main线程先去忙其他任务* -----1秒钟后出结果:0* -------计算完成,更新系统UpdateValue:0** */

3.4 案例精讲-从电商网站的比价需求展开

3.4.1 函数式编程已成为主流

  • Lambda表达式+Stream流式调用+Chain链式调用+Java8函数式编程

  • 函数式接口

    • Runnable:无参数、无返回值
    • Function:接受一个参数,并且有返回值
    • Consumer:接受一个参数,没有返回值
    • BiConsumer:接受两个参数,没有返回值
    • Supplier:没有参数,有返回值
  • 小结
    在这里插入图片描述

  • chain链式调用

/*** @package: com.yunyang.bilibili.juc.completablefeture* @description: Chain链式调用演示* @author: Yunyang* @date: 2024/10/17  22:06* @version:1.0**/
public class CompletableFutureMallDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {final Student student = new Student();student.setId(1).setStudentName("zhangsan").setMajor("english"); //链式调用}
}@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true) //开启链式调用
class Student{private Integer id;private String studentName;private String major;
}

3.4.2 join和get对比

  • join和get对比
/*** @package: com.yunyang.bilibili.juc.completablefeture* @description:join和get对比* @author: Yunyang* @date: 2024/10/17  22:06* @version:1.0**/
public class CompletableFutureMallDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {final CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {return "hello 1234";});System.out.println(completableFuture.get());System.out.println(completableFuture.join());// join和get的作用大致相同,其区别是在编译时是否报出检查时异常}
}
  • join和get的作用大致相同,其区别是在编译时是否报出检查时异常:
    • get在编译时会报出异常;
    • join则不会报出异常。

3.4.3 大厂业务需求说明

切记:功能—>性能(完成—>完美)
电商网站比价需求分析:

  1. 需求说明:
    a. 同一款产品,同时搜索出同款产品在各大电商平台的售价
    b. 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
  2. 输出返回:
    a. 出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List
    例如:《Mysql》 in jd price is 88.05 《Mysql》 in taobao price is 90.43
  3. 解决方案,对比同一个产品在各个平台上的价格,要求获得一个清单列表
    a. step by step,按部就班,查完淘宝查京东,查完京东查天猫…
    b. all in,万箭齐发,一口气多线程异步任务同时查询

3.4.4 一波流Java8函数式编程带走-比价案例实战Case


/*** 这里面需要注意一下Stream流方法的使用* 这种异步查询的方法大大节省了时间消耗,可以融入简历项目中,和面试官有所探讨*/
public class CompletableFutureMallDemo2 {static List<NetMall> list = Arrays.asList(new NetMall("jd"),new  NetMall("dangdang"),new NetMall("taobao"));/*** step by step* @param list* @param produceName* @return*/public static List<String> getPrice(List<NetMall> list, String produceName){//《Mysql》 in jd price is 88.05return list.stream().map(netMall ->String.format("《" +produceName + "》 in %s price is %.2f",netMall.getNewMallName(),netMall.calcPrice(produceName))).collect(Collectors.toList());}/***all in* 把list里面的内容映射给CompletableFuture()* @param list* @param productName* @return*/public static List<String> getPriceByCompletableFuture(List<NetMall> list,String productName){return list.stream().map(netMall -> CompletableFuture.supplyAsync(()->String.format("《" + productName + "》 in %s price is %.2f",netMall.getNewMallName(),netMall.calcPrice(productName)))) //Stream<CompletableFuture<String>>.collect(Collectors.toList()) //List<CompletableFuture<String>>.stream() //Stream<String>.map(stringCompletableFuture -> stringCompletableFuture.join()) //List<String>.collect(Collectors.toList());}public static void main(String[] args) {/*** 采用step by setp方式查询* 《mysql》 in jd price is 110.72* 《mysql》 in dangdang price is 109.34* 《mysql》 in taobao price is 109.66* ---costTime: 3079毫秒*/long startTime = System.currentTimeMillis();final List<String> list1 = getPrice(list, "mysql");for (String element : list1) {System.out.println(element);}long endTime = System.currentTimeMillis();System.out.println("---costTime: " + (endTime - startTime) + "毫秒");System.out.println("**************************************");/*** 采用 all in 三个异步线程方式查询* 《mysql》 in jd price is 110.79* 《mysql》 in dangdang price is 110.61* 《mysql》 in taobao price is 110.30* ---costTime: 1029毫秒*/long startTime2 = System.currentTimeMillis();final List<String> list2 = getPriceByCompletableFuture(list, "mysql");for (String element : list2) {System.out.println(element);}long endTime2 = System.currentTimeMillis();System.out.println("---costTime: " + (endTime2 - startTime2) + "毫秒");}
}class NetMall{@Getterprivate String newMallName;public NetMall(String newMallName) {this.newMallName = newMallName;}public double calcPrice(String productName){try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);}
}

4 CompletableFuture常用方法

4.1 获得结果和触发计算

4.1.1 获取结果

  1. public T get()

    • 描述: 这是一个阻塞方法,它会一直等待直到计算完成并返回结果。如果计算过程中抛出异常,get() 方法会将异常重新抛出。
    • 特点: 不见不散,即必须等到计算完成才能获取结果。
  2. public T get(long timeout, TimeUnit unit)

    • 描述: 这也是一个阻塞方法,但它会在指定的时间内等待计算完成。如果在指定时间内计算未完成,它会抛出 TimeoutException
    • 特点: 过时不候,即如果在指定时间内未完成,则抛出异常。
  3. public T join()

    • 描述: 类似于 get() 方法,但它不会抛出检查型异常(如 InterruptedExceptionExecutionException),而是直接抛出非检查型异常(如 RuntimeException)。
    • 特点: 不抛出异常,适合在不需要处理检查型异常的场景中使用。
  4. public T getNow(T valueIfAbsent)

    • 描述: 这是一个非阻塞方法,它会立即返回结果。如果计算已经完成,则返回计算结果;如果计算尚未完成,则返回传入的 valueIfAbsent 值。
    • 特点: 立即获取结果,不阻塞。如果计算未完成,返回设定的默认值。

4.1.2 主动触发计算

  1. public boolean complete(T value)
    • 描述: 这个方法用于主动完成计算,并设置计算结果为传入的 value。如果计算尚未完成,调用此方法后,所有等待结果的线程将立即获得这个 value。如果计算已经完成,则此方法不会产生任何影响,并返回 false
    • 特点: 是否打断 get() 方法立即返回括号值。如果计算未完成,调用 complete(value) 后,所有 get() 方法调用将立即返回 value

4.1.3 示例代码

/*** @package: yunyang.bilibili.juc.completablefeture* @description: 获得结果和触发计算* @author: Yunyang* @date: 2024/10/18  20:59* @version:1.0**/
public class CompletableFutureAPIDemo {public static void main(String[] args) //throws ExecutionException, InterruptedException, TimeoutException{final CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}return "abc";});// System.out.println(completableFuture.get());  // 不见不散// System.out.println(completableFuture.get(2L, TimeUnit.SECONDS)); // 过时不候// System.out.println(completableFuture.join()); // 不抛出异常try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}// System.out.println(completableFuture.getNow("xxxx")); // 立即获取结果System.out.println(completableFuture.complete("completeValue") + "\t" + completableFuture.join()); // 主动触发计算}
}

4.1.4 小结

  • 获取结果: 你可以使用 get(), get(timeout), join(), 和 getNow() 方法来获取异步计算的结果。

  • 主动触发计算: 使用 complete(value) 方法可以主动完成计算并设置结果,打断所有等待结果的线程。

4.2 对计算结果进行处理

4.2.1 thenApply

  • 描述: thenApply 方法用于在 CompletableFuture 完成后对结果进行转换或处理。它接收一个 Function 作为参数,该函数将前一个 CompletableFuture 的结果作为输入,并返回一个新的结果。
  • 特点:
    • 串行化: thenApply 方法会等待前一个 CompletableFuture 完成后再执行。
    • 异常处理: 如果前一个 CompletableFuture 抛出异常,thenApply 不会执行,整个链路会被中断。

4.2.2 handle

  • 描述: handle 方法也用于在 CompletableFuture 完成后对结果进行处理,但它可以处理异常。它接收一个 BiFunction 作为参数,该函数接收两个参数:前一个 CompletableFuture 的结果(或异常)和可能的异常。
  • 特点:
    • 串行化: handle 方法会等待前一个 CompletableFuture 完成后再执行。
    • 异常处理: 即使前一个 CompletableFuture 抛出异常,handle 方法仍然会执行,并且可以对异常进行处理。

4.2.3 示例代码

/*** @package: yunyang.bilibili.juc.completablefeture* @description:对计算结果进行处理* @author: Yunyang* @date: 2024/10/18  21:16* @version:1.0**/
public class CompletableFutureAPI2Demo {public static void main(String[] args) {// testThenApply();testHandle();}/*** 使用 handle*/private static void testHandle() {final ExecutorService threadPool = Executors.newFixedThreadPool(3);CompletableFuture.supplyAsync(()->{try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("1111");return 1;},threadPool).handle((f,e)->{int i = 1/0;System.out.println("2222"); // 有异常也可以往下一步走,根据带的异常参数可以进一步处理return f + 2;}).handle((f,e)->{System.out.println("3333");return f + 3;}).whenComplete((v,e)->{if(e==null){System.out.println("----计算结果:"+v);}}).exceptionally(e->{e.printStackTrace();System.out.println(e.getMessage());return null;});System.out.println(Thread.currentThread().getName() + "----主线程去忙其他任务了");threadPool.shutdown();}/*** 使用 thenApply*/private static void testThenApply() {final ExecutorService threadPool = Executors.newFixedThreadPool(3);CompletableFuture.supplyAsync(()->{try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("111");return 1;},threadPool).thenApply(f->{int i = 1/0;System.out.println("222"); // 由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停return f + 2;}).thenApply(f->{System.out.println("333");return f + 3;}).whenComplete((v,e)->{if(e == null){System.out.println("----计算结果:" +v);}}).exceptionally(e->{e.printStackTrace();System.out.println(e.getMessage());return null;});System.out.println(Thread.currentThread().getName() + "-----主线程去忙其他任务了");threadPool.shutdown();}
}

4.2.4 总结对比

特性thenApplyhandle
串行化
异常处理中断链路,不执行后续步骤继续执行,可以处理异常
适用场景结果依赖,且不需要处理异常结果依赖,且需要处理异常
函数签名Function<T, U>BiFunction<T, Throwable, U>
异常传递不传递异常,中断链路传递异常,可以进一步处理

4.2.5 小结

  • thenApply: 适用于结果依赖且不需要处理异常的场景。如果前一个步骤抛出异常,整个链路会被中断。
  • handle: 适用于结果依赖且需要处理异常的场景。即使前一个步骤抛出异常,链路仍然可以继续执行,并且可以对异常进行处理。

4.3 对计算结果进行消费

4.3.1 thenAccept

  • 描述: thenAccept 方法用于在 CompletableFuture 完成后对结果进行消费,它接收一个 Consumer 作为参数,该函数将前一个 CompletableFuture 的结果作为输入,并执行一些操作,但不会返回任何结果。
  • 特点:
    • 串行化: thenAccept 方法会等待前一个 CompletableFuture 完成后再执行。
    • 无返回值: thenAccept 方法不会返回任何结果,它只是消费前一个任务的结果。

4.3.2 thenRun

  • 描述: thenRun 方法用于在 CompletableFuture 完成后执行一个 Runnable 任务,它不接收前一个任务的结果,也不返回任何结果。
  • 特点:
    • 串行化: thenRun 方法会等待前一个 CompletableFuture 完成后再执行。
    • 无参数,无返回值: thenRun 方法不接收前一个任务的结果,也不返回任何结果。

4.3.3 示例代码

/*** @package: yunyang.bilibili.juc.completablefeture* @description:对计算结果进行消费* @author: Yunyang* @date: 2024/10/18  21:35* @version:1.0**/
public class CompletableFutureAPI3Demo {public static void main(String[] args) {// testThenAccept();System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> { }).join()); // 任务A执行完执行B,B不需要A的结果System.out.println(CompletableFuture.supplyAsync(() -> "acceptA").thenAccept(r -> System.out.println(r)).join()); // 任务A执行完执行B,B需要A的结果,但是任务B无返回值System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(r -> r + "reslutB").join()); // 任务A执行完执行B,B需要A的结果,同时任务B有返回值}private static void testThenAccept() {CompletableFuture.supplyAsync(()->{return 1;}).thenApply(f->{return f + 2;}).thenApply(f->{return f + 3;}).thenAccept(System.out::println);}
}

4.3.4 对比补充

方法参数类型返回值类型描述
thenRunRunnablevoid任务A执行完执行B,B不需要A的结果
thenAcceptConsumervoid任务A执行完执行B,B需要A的结果,但是任务B无返回值
thenApplyFunctionU任务A执行完执行B,B需要A的结果,同时任务B有返回值

4.3.5 CompletableFuture 和线程池说明

CompletableFuture 提供了同步和异步版本的这些方法,它们在使用线程池的方式上有所不同。

4.3.5.1 thenRunthenRunAsync 的区别
  • thenRun(Runnable runnable): 同步执行,使用与前一个任务相同的线程池。
  • thenRunAsync(Runnable runnable): 异步执行,使用默认的 ForkJoinPool 线程池,除非你传入了自定义线程池。
4.3.5.2 示例代码
/*** @package: yunyang.bilibili.juc.completablefeture* @description:CompletableFuture和线程池说明* @author: Yunyang* @date: 2024/10/18  21:51* @version:1.0**/
public class CompletableFututeThreadPoolDemo {public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {ExecutorService threadPool = Executors.newFixedThreadPool(5);try {CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.MICROSECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("1号任务:" + "\t" + Thread.currentThread().getName());return "abcd";},threadPool).thenRunAsync(() -> {try {TimeUnit.MICROSECONDS.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("2号任务:" + "\t" + Thread.currentThread().getName());}).thenRun(() -> {try {TimeUnit.MICROSECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("3号任务:" + "\t" + Thread.currentThread().getName());}).thenRun(() -> {try {TimeUnit.MICROSECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("4号任务:" + "\t" + Thread.currentThread().getName());});completableFuture.get(2L,TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);} catch (TimeoutException e) {throw new RuntimeException(e);} finally {threadPool.shutdown();}}
}

4.3.6 小结

  • 没有传入自定义线程池: 都使用默认的 ForkJoinPool 线程池。
  • 传入自定义线程池:
    • 如果你执行第一个任务时传入了自定义线程池:
      • thenRun 方法执行第二个任务时,第二个任务与第一个任务共用同一个线程池。
      • thenRunAsync 方法执行第二个任务时,第一个任务使用自定义线程池,第二个任务使用 ForkJoinPool 线程池。

4.3.7 备注

  • 处理速度过快时,系统可能会优化并直接使用 main 线程处理任务。
  • 其他方法如 thenAcceptthenAcceptAsyncthenApplythenApplyAsync 等,它们之间的区别也是类似的。

4.3.8 源码分析

CompletableFuture 的源码中,thenRunthenRunAsync 方法的实现如下:

public CompletableFuture<Void> thenRun(Runnable action) {return uniRunStage(null, action);
}public CompletableFuture<Void> thenRunAsync(Runnable action) {return uniRunStage(asyncPool, action);
}public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor) {return uniRunStage(screenExecutor(executor), action);
}
  • thenRun 方法使用 null 作为线程池参数,表示使用前一个任务的线程池。
  • thenRunAsync 方法使用 asyncPool,即默认的 ForkJoinPool 线程池,除非你传入了自定义线程池。

4.4 对计算速度进行选用

CompletableFuture 中,applyToEither 方法用于处理两个 CompletableFuture 中的任意一个先完成的任务。这个方法非常适合在需要快速响应的场景中使用,即“谁快用谁”的策略。

4.4.1 applyToEither

  • 描述: applyToEither 方法接收两个 CompletableFuture 和一个 Function,当其中一个 CompletableFuture 完成时,applyToEither 会立即执行 Function,并将完成的结果作为输入,返回一个新的 CompletableFuture
  • 特点:
    • 非阻塞: applyToEither 是非阻塞的,它会立即返回一个新的 CompletableFuture
    • 快速响应: 它会使用先完成的 CompletableFuture 的结果。

4.4.2 示例代码

/*** @package: yunyang.bilibili.juc.completablefeture* @description:对计算速度进行选用* @author: Yunyang* @date: 2024/10/18  22:17* @version:1.0**/
public class CompletableFutureFastDemo {public static void main(String[] args) {final CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {System.out.println("A is come in");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}return "playA";});final CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {System.out.println("B is come in");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}return "playB";});final CompletableFuture<String> result = playA.applyToEither(playB, f -> {return f + " is winner";});System.out.println(Thread.currentThread().getName() + " : " + result.join());}
}

4.4.3 解释

  1. 创建两个 CompletableFuture:

    • playA 模拟一个耗时 2 秒的任务。
    • playB 模拟一个耗时 1 秒的任务。
  2. 使用 applyToEither:

    • applyToEither 方法会等待 playAplayB 中的任意一个先完成。
    • 一旦其中一个 CompletableFuture 完成,applyToEither 会立即执行传入的 Function,并将完成的结果作为输入。
    • 在这个例子中, playB 会先完成,因此 applyToEither 会使用 playB 的结果。
  3. 获取最终结果:

    • result.join() 会返回处理后的结果。

4.4.4 小结

  • applyToEither: 用于处理两个 CompletableFuture 中的任意一个先完成的任务,非常适合需要快速响应的场景。
  • 非阻塞: applyToEither 是非阻塞的,它会立即返回一个新的 CompletableFuture
  • 快速响应: 它会使用先完成的 CompletableFuture 的结果。

4.5 对计算结果进行合并

CompletableFuture 中,thenCombine 方法用于在两个 CompletableFuture 都完成后,将它们的结果合并在一起进行处理。这个方法非常适合在需要等待多个异步任务都完成后,再进行进一步处理的场景。

4.5.1 thenCombine

  • 描述: thenCombine 方法接收两个 CompletableFuture 和一个 BiFunction,当这两个 CompletableFuture 都完成后,thenCombine 会立即执行 BiFunction,并将两个 CompletableFuture 的结果作为输入,返回一个新的 CompletableFuture
  • 特点:
    • 非阻塞: thenCombine 是非阻塞的,它会立即返回一个新的 CompletableFuture
    • 等待所有任务完成: 它会等待两个 CompletableFuture 都完成后,再进行合并处理。

4.5.2 示例代码

/*** @package: yunyang.bilibili.juc.completablefeture* @description:对计算结果进行合并* @author: Yunyang* @date: 2024/10/18  22:25* @version:1.0**/
public class CompletableFutureCombineDemo {public static void main(String[] args) {// testCombine1();testCombine2();}private static void testCombine2() {final CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName() + ":" + " come in 1");return 10;}).thenCombine(CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName() + ":" + "come in 2");return 20;}), (x, y) -> {System.out.println(Thread.currentThread().getName() + ":" + "come in 3");return x + y;}).thenCombine(CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName() + ":" + "come in 4");return 30;}), (a, b) -> {System.out.println(Thread.currentThread().getName() + ":" + "come in 5");return a + b;});System.out.println("----主线程结束,end");System.out.println(completableFuture.join());}private static void testCombine1() {CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName() + "\t---启动");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}return 10;});CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName() + "\t---启动");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}return 20;});CompletableFuture<Integer> result = completableFuture1.thenCombine(completableFuture2, (x, y) -> {System.out.println("----开始两个结果合并");return x + y;});System.out.println(result.join());}
}

4.5.3 小结

  • thenCombine: 用于在两个 CompletableFuture 都完成后,将它们的结果合并在一起进行处理。
  • 非阻塞: thenCombine 是非阻塞的,它会立即返回一个新的 CompletableFuture
  • 等待所有任务完成: 它会等待两个 CompletableFuture 都完成后,再进行合并处理。

5 思维导图

在这里插入图片描述

6 参考链接

  1. 【尚硅谷JUC并发编程(对标阿里P6-P7)】
  2. CompletableFuture

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/451807.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP(三次握手)和UDP(面向无连接)的原理以及区别

TCP(三次握手)和UDP&#xff08;面向无连接&#xff09;的原理以及区别 网络协议是每个前端工程师都必须要掌握的知识&#xff0c;TCP/IP 中有两个具有代表性的传输层协议。 概述 &#x1f4e1;TCP&#xff08;Transmission Control Protocol&#xff09;是一种网络协议&#…

【opengles】笔记1:屏幕坐标与归一化坐标(NDC)的转换

参考资料 OpenGL希望在所有顶点着色器运行后&#xff0c;所有我们可见的顶点都变为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说&#xff0c;每个顶点的x&#xff0c;y&#xff0c;z坐标都应该在-1.0到1.0之间&#xff0c;超出这个坐标范围的顶点都将不可见。…

【Python】NumPy(二):数组运算、数据统计及切片索引、广播机制

目录 Numpy数组 数组的基本运算 乘法 加法 数组的数据统计 平均值 中位数 最大值和最小值 求和 累积和 标准差 方差 切片和索引 索引 一维数组的索引 二维数组的索引 获取多个元素 布尔索引 切片 一维数组切片 二维数组切片 多维数组切片 广播机制 规则 …

Seata序列化优化

Apache Seata(incubating) 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 本篇文章主要介绍Seata序列化实现优化。Seata对于网络传输数据&#xff0c;提供了多种序列化实现&#xff0c;包含Seata自身的序列化实现、…

一元n次多项式加法【数据结构-链表】

一元n次多项式定义如下&#xff1a; 其中Ai​为实数&#xff0c;i为不小于0的整数。在完成“一元n次多项式输入输出”题目的基础上实现一元n次多项式的加法。要求用链表实现上述一元n次多项式的操作。 输入格式: 有两个一元n次多项式&#xff0c;例如分别为&#xff1a; f(X)…

80.【C语言】数据结构之时间复杂度

目录 1.数据结构的定义 2.算法的定义 3.算法的效率 1.衡量一个算法的好坏的方法 例题:计算以下代码的循环次数 2.大O的渐进表示法 练习1:求下列代码的时间复杂度 练习2:求下列代码的时间复杂度 练习3:求下列代码的时间复杂度 练习4:求下列代码的时间复杂度 4.总结:计…

Leetcode—1115. 交替打印 FooBar【中等】(多线程)

2024每日刷题&#xff08;180&#xff09; Leetcode—1115. 交替打印 FooBar C实现代码 class FooBar { private:int n;sem_t fooSem;sem_t barSem;public:FooBar(int n) {this->n n;sem_init(&fooSem, 0, 1);sem_init(&barSem, 0, 0);}~FooBar() {sem_destroy(&…

mac安装brew时踩坑解决方案

安装包 mac上如果按照git等工具可能会使用brew&#xff0c;例如使用&#xff1a;$ brew install git命令&#xff0c;如果电脑没有按照brew&#xff0c;则会提示&#xff1a;zsh: command not found: brew 解决方案 需要我们打开brew的官网https://brew.sh/&#xff0c;复制…

机器学习|Pytorch实现天气预测

机器学习|Pytorch实现天气预测 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 电脑系统&#xff1a;Windows11 显卡型号&#xff1a;NVIDIA Quadro P620 语言环境&#xff1a;python 3.9.7 编译器&#x…

得物App3D创新应用引关注,世界设计之都大会启幕

近日&#xff0c;2024世界设计之都大会&#xff08;WDCC&#xff09;在上海盛大启幕。此次大会以“设计无界 新质生长”为主题&#xff0c;汇聚了全球设计领域的精英与前沿成果&#xff0c;展现了设计作为新质生产力的巨大潜力。主场展览占据了整整3个楼面&#xff0c;总面积达…

进程间关系与守护进程

一、进程组 1.1、什么是进程组 提到进程的概念&#xff0c; 其实每一个进程除了有一个进程 ID(PID)之外 还属于一 个进程组。进程组是一个或者多个进程的集合&#xff0c; 一个进程组可以包含多个进程。 每一 个进程组也有一个唯一的进程组 ID(PGID)&#xff0c; 并且这个 PG…

SCI英文文献阅读工具【全文翻译】【逐句翻译】

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 SCI英文文献阅读工具【全文翻译】【逐句翻译】 1. 全文翻译【DeepL】 适用于泛读网址&#xff1a;https://www.deepl.com/zh/translator/files 1.1 前提 文档大小&#xff1a;pdf文档不超过5M&#xff08;可先…

设计模式05-创建型模式(建造者/原型/单例模式/Java)

3.4 建造者模式 3.4.1 建造者模式的定义 动机&#xff1a;方便创建复杂对象&#xff08;一个对象中包含多个成员变量&#xff09; 定义&#xff1a;将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂…

计算机视觉中的最小二乘法:寻找完美交点和直线拟合

Hello&#xff0c;小伙伴们&#xff01;今天我们要聊的是计算机视觉中的一个小技巧——使用最小二乘法来进行交点计算和直线拟合。你有没有想过&#xff0c;如何从一堆杂乱无章的数据点中找到那条最佳拟合直线&#xff1f;或者&#xff0c;如何确定几条直线相交的确切位置&…

OpenCV物体跟踪:使用CSRT算法实现实时跟踪

目录 简介 CSRT算法简介 实现步骤 1. 初始化摄像头和跟踪器 2. 读取视频帧和初始化跟踪 3. 实时跟踪和显示结果 4. 显示和退出 5、结果展示 总结 简介 在计算机视觉和视频处理领域&#xff0c;物体跟踪是一项核心技术&#xff0c;它在监控、人机交互、运动分析等方面…

CSS布局/简单应用

思考下面四个图片如何布局 test1 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

双十一有啥好用的物品可以推荐购买?2024不可错过的必囤好物清单!

双十一购物狂欢节即将拉开帷幕&#xff0c;许多朋友们可能还在犹豫不决&#xff0c;不知道应该选购哪些商品。别担心&#xff0c;今天我特意为大家精心准备了一份包含五款必囤好物的清单&#xff0c;希望能够帮助大家在双十一期间抢购到心仪的商品&#xff0c;享受购物的乐趣&a…

《米小圈动画成语》|在趣味中学习,在快乐中掌握成语知识!

作为一名家长&#xff0c;我一直希望孩子能够在学习的过程中既感受到乐趣&#xff0c;又能获得真正的知识。成语作为中华文化的精华&#xff0c;虽然意义深远、简洁凝练&#xff0c;但对于一个小学生来说&#xff0c;学习和理解这些言简意赅的成语无疑是一个挑战。尤其是有些成…

将本地文件上传到GIT上

上传文件时&#xff0c;先新建一个空文件&#xff0c;进行本地库初始化&#xff0c;再进行远程库克隆&#xff0c;将要上传的文件放到克隆下来的文件夹里边&#xff0c;再进行后续操作 1.在本地创建文件夹&#xff0c;将要上传的文件放在该文件下 2.在该文件页面中打开Git Bas…

ai字幕用什么软件制作?6款视频加字幕工具分享!

在视频制作和后期处理中&#xff0c;字幕的添加是一个重要的环节。随着AI技术的发展&#xff0c;越来越多的软件开始支持AI自动加字幕功能&#xff0c;使得字幕的制作变得更加简单和高效。本文将为大家介绍几款常用的AI字幕制作软件&#xff0c;并详细讲解如何使用AI自动加字幕…