Bootstrap

【JUC】使用CompletableFuture执行异步任务

Future接口介绍

在这里插入图片描述

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

在这里插入图片描述

  • Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业条。比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(如老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。

Future接口常用实现类FutureTask

Future接口能干什么

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

Future接口相关架构

在这里插入图片描述

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

Runnable接口和Callable接口的区别

在这里插入图片描述

Thread要创建一个线程,只能接受Runnable接口,不能满足有返回值、异形任务(需要满足能获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等特点)的需求。

在这里插入图片描述

在这里插入图片描述

RunnableFuture同时有了Runnable和Future的功能,即能实现多线程、异步任务

在这里插入图片描述

RunnableFuture有一个实现类FutureTask,FutureTask可以通过构造方法传入Callable,所以可以同时实现多线程、有返回、异步任务

在这里插入图片描述

在这里插入图片描述

FutureTask初步使用

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.start();
        // 获取返回值 返回hello Callable
        System.out.println(futureTask.get()); 
    }
}
class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("--------come in call");
        return "hello Callable";
    }
}

在这里插入图片描述

Future编码实战和优缺点分析

优点

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

public class FutureThreadPoolDemo {
    /**
     * 3个任务,目前开启多个异步任务线程来处理,请问耗时多少?883毫秒
     * @param args
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        long startTime = System.currentTimeMillis();
        FutureTask<String> futureTask1 = new FutureTask<String>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task1 over";
        });
        threadPool.submit(futureTask1);
        FutureTask<String> futureTask2 = new FutureTask<String>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task2 over";
        });
        threadPool.submit(futureTask2);
        System.out.println(futureTask1.get());
        System.out.println(futureTask2.get());

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒");

        System.out.println(Thread.currentThread().getName() + "\t -----end");
        threadPool.shutdown();
    }

    /**
     * 3个任务,目前只有一个线程main来处理,请问耗时多少?1131毫秒
     */
    private static void m1() {

        long startTime = System.currentTimeMillis();
        //暂停毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒");

        System.out.println(Thread.currentThread().getName() + "\t -----end");
    }
}

缺点

获取结果的方式不优雅
  • get()阻塞:一旦调用get()方法求结果,一旦调用就要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
public class FutureApiDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "--------come in");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        // 这样会有阻塞的可能,在程序没有计算完毕的情况下。
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName() + " ------忙其他任务");
    }
}

在这里插入图片描述

可以设置等待三秒,futureTask.get(3,TimeUnit.SECONDS),过时会抛出超时异常,其他代码捕获到这个异常可以继续执行,但是在实际场景中可能会留下大量的日志,不推荐使用

在这里插入图片描述

  • isDone()轮询:轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。
public class FutureApiDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "--------come in");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
//        System.out.println(futureTask.get());//这样会有阻塞的可能,在程序没有计算完毕的情况下。
        System.out.println(Thread.currentThread().getName() + " ------忙其他任务");
//        System.out.println(futureTask.get(3,TimeUnit.SECONDS));//只愿意等待三秒,计算未完成直接抛出异常
        while (true) {//轮询
            if(futureTask.isDone()){
                System.out.println(futureTask.get());
                break;
            }else{
                // 500毫秒之后再看看有没有结果
                TimeUnit.MILLISECONDS.sleep(500);
                System.out.println("正在处理中,不要催了,越催越慢,再催熄火");
            }
        }
        /* 轮询结果
        * main ------忙其他任务
        t1--------come in
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        正在处理中,不要催了,越催越慢
        task over
        Process finished with exit code 0
        * */
    }
}
结论
  • Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。(CompletableFuture诞生的原因)

完成一些复杂的任务

需求:

  • 对于简单的业务场景使用Future完全ok,高并发业务使用的话,阻塞和轮询都会影响性能
  • 通过轮询的方式去判断任务是否完成这样非常占cpu并且代码也不优雅。希望可以有回调通知,即应对Future的完成时间,完成了可以告诉我
  • 创建异步任务:Future+线程池组合
  • 多个任务前后依赖可以组合处理
    • 想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值
    • 想将两个或多个异步计算合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果(水煮鱼:买鱼—>调料—>下锅)
  • 对计算速度选最快的:当Future集合中某个任务最快结束时,返回该结果

结论

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

CompletableFuture对Future的改进

CompletableFuture为什么会出现

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

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

CompletableFuture和CompletionStage介绍

类架构说明

在这里插入图片描述

  • 接口CompletionStage
    • 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段(例如代码执行节点->代码完成,发送通知->有异常,抛异常)
    • 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:stage.thenApply(x-> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()
    • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
  • 类CompletableFuture
    • 提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
    • 它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作

创建异步任务的四个静态方法

在这里插入图片描述

对于Executor参数说明:

  • 若没有指定,则使用默认的ForkJoinPoolcommonPool()作为它的线程池执行异步代码
  • 如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码

runAsync

传的是Runnable,所以没有返回值

public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor);

建议都指定线程池,不然主线程运行完成之后,默认线程池会跟着关闭,这样异步任务就停止执行了

supplyAsync

有返回值

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor);

实例

public class CompletableFutureBuildDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        
        // 无返回值
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            // 不指定线程池,输出类似于ForkJoinPool. commonPool-worker-1
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },executorService);
        // 输出null
        System.out.println(completableFuture.get()); 
        
        // 有返回值
        CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello supplyAsync";
        },executorService);
        // hello supplyAsync
        System.out.println(objectCompletableFuture.get());
        
        // 关闭线程池
        executorService.shutdown();
    }
}

有返回值的方法输出

在这里插入图片描述

任务完成回调+任务异常

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

public class CompletableFutureUseDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        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) {
                e.printStackTrace();
            }
            // 模拟产生异常情况
            if (result > 5) { 
                int i = 10 / 0;
            }
            System.out.println("----------1秒钟后出结果" + result);
            return result;
        }, executorService).whenComplete((v, e) -> {
            // v 上一步的result
            // e 异常
            if (e == null) {
                // 没有异常
                System.out.println("计算完成 获取值" + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
            return null;
        });
        System.out.println(Thread.currentThread().getName() + "先去忙其他任务");
        executorService.shutdown();
    }
}
/**
无异常情况
pool-1-thread-1---come in
main先去忙其他任务
----------1秒钟后出结果2
计算完成 获取值2
*/
/**
有异常情况
*pool-1-thread-1---come in
main先去忙其他任务
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
异常情况:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero
*/

主线程创建异步任务之后,就继续干自己的事情,异步任务用另外的线程去做事,做完之后可以通知主线程

CompletableFuture优点

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

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

函数式编程已成为主流

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

函数式接口

在这里插入图片描述

在这里插入图片描述

  • Runnable:无参数、无返回值

在这里插入图片描述

  • Function:接受一个参数,并且有一个返回值

在这里插入图片描述

  • Consumer:接受一个参数,没有返回值

在这里插入图片描述

  • BiConsumer:接受两个参数,没有返回值

在这里插入图片描述

两个输入参数,没有返回值。在whenCompelete中,需要放一个泛型来接收返回值,一个异常

在这里插入图片描述

在这里插入图片描述

  • Supplier:没有参数,有一个返回值

在这里插入图片描述

在这里插入图片描述

chain链式调用

public class CompletableFutureMallDemo {
    public static void main(String[] args) {
        Student student = new Student();
        student.setId(1).setStudentName("z3").setMajor("english"); //链式调用
    }
}

@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)//开启链式调用
class Student {
    private Integer id;
    private String studentName;
    private String major;
}

大厂业务需求说明

切记:功能—>性能(完成—>完美)

电商网站比价需求分析:

1、需求说明:

  1. 同一款产品,同时搜索出同款产品在各大电商平台的售价
  2. 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少

2、输出返回:出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List

  • 《Mysql》 in jd price is 88.05
  • 《Mysql》 in dangdang price is 86.11
  • 《Mysql》 in taobao price is 90.43

3、解决方案,对比同一个产品在各个平台上的价格,要求获得一个清单列表

  1. step by step,按部就班,查完淘宝查京东,查完京东查天猫…
  2. all in,万箭齐发,一口气多线程异步任务同时查询

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

/**
这里面需要注意一下Stream流方法的使用
这种异步查询的方法大大节省了时间消耗,可以融入简历项目中,和面试官有所探讨
*/
public class CompletableFutureMallDemo {
    static List<NetMall> list = Arrays.asList(new NetMall("jd"), new NetMall("dangdang"), new NetMall("taobao"), new NetMall("dangdang"));
    
    /**
     * step by step
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPrice(List<NetMall> list, String productName) {
        //《Mysql》 in jd price is 88.05
        return list
                .stream()
                .map(netMall ->
                        String.format("《" + productName + "》" + "in %s price is %.2f",
                                netMall.getNetMallName(),
                                netMall.calcPrice(productName)))
                .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.getNetMallName(),
                                        netMall.calcPrice(productName)))) //Stream<CompletableFuture<String>>
                .collect(Collectors.toList()) //List<CompletableFuture<String>>
                .stream()//Stream<String>
                .map(s -> s.join()).collect(Collectors.toList()); //List<String>
    }
    
    public static void main(String[] args) {
        /**
         * 采用step by setp方式查询
         * 《masql》in jd price is 110.23
         * 《masql》in dangdang price is 109.34
         * 《masql》in taobao price is 109.31
         * ------costTime: 3131 毫秒
         */
        long StartTime = System.currentTimeMillis();
        List<String> list1 = getPrice(list, "masql");
        for (String element : list1) {
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒");
        
          /**
         * 采用 all in三个异步线程方式查询
         * 《mysql》in jd price is 109.31
         * 《mysql》in dangdang price is 109.67
         * 《mysql》in taobao price is 110.30
         * ------costTime1024 毫秒
         */
        long StartTime2 = System.currentTimeMillis();
        List<String> list2 = getPriceByCompletableFuture(list, "mysql");
        for (String element : list2) {
            System.out.println(element);
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("------costTime" + (endTime2 - StartTime2) + " 毫秒");
    }
}

@AllArgsConstructor
@NoArgsConstructor
@Data
// 电商平台
class NetMall {
    private String netMallName;
    public double calcPrice(String productName) {
        try {
            // 模拟需要一秒查询出来
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // ThreadLocalRandom.current().nextDouble() 0到1之间的一个数字
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

CompletableFuture常用方法

获得结果和触发计算

获取结果getjoingetNow

  • public T get()

  • public T get(long timeout,TimeUnit unit):如system.out.println(completableFuture.get(2L, TimeUnit.SECONDS));

  • public T join() :和get一样,都是获取结果,只是不需要抛出异常

  • public T getNow(T valuelfAbsent) :计算完成就返回正常值,否则返回传入的valueIfAbsent,立即获取结果不阻塞

【源码】

     /**
      * Returns the result value (or throws any encountered exception)
      * if completed, else returns the given valueIfAbsent.
      *
      * @param valueIfAbsent the value to return if not completed
      * @return the result value, if completed, else the given valueIfAbsent
      * @throws CancellationException if the computation was cancelled
      * @throws CompletionException if this future completed
      * exceptionally or a completion computation threw an exception
      */
     public T getNow(T valueIfAbsent) {
         Object r;
         return ((r = result) == null) ? valueIfAbsent : reportJoin(r);
     }

在这里插入图片描述

join()和get()的区别

join()和get()的作用都是取值,两者的区别是在编译期间是否会做检查型异常处理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

主动触发计算complete

  • public boolean complete(T value) :是否打断get方法立即返回传入的参数,返回是否打断成功
private static void group1() throws InterruptedException, ExecutionException, TimeoutException {
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        //暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "abc";
    });

    //System.out.println(completableFuture.get());
//        System.out.println(completableFuture.get(1L, TimeUnit.SECONDS));
    //System.out.println(completableFuture.join());

    //暂停几秒钟线程
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //System.out.println(completableFuture.getNow("xxx"));
    System.out.println(completableFuture.complete("completeValue") + "\t" + completableFuture.get());
}

运行结果如下:

  • 异步任务执行需要两秒,过了一秒就通过join获取结果,本来是要阻塞的,complete方法把这个过程打断了,并将结果值设置为传入的completeValue

在这里插入图片描述

对计算结果进行处理(串行化)

thenApply

  • thenApply:计算结果存在依赖关系,两个线程需要串行化(如先买鱼,再煮鱼),由于存在依赖关系,当前步骤有异常的话就叫停(当前步错,不走下一步)
public class CompletableFutureApiDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        }, threadPool).thenApply(f -> {
            System.out.println("222");
            return f + 2;
        }).thenApply(f -> {
           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.getCause());
            return null;
        });
        System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
    }
}

在这里插入图片描述

handle

  • handle:计算结果存在依赖关系,两个线程串行化,功能类似于thenApply,但有异常也可以往下走一步

在这里插入图片描述

public class CompletableFutureApiDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        }, threadPool).thenApply(f -> {
            System.out.println("222");
            return f + 2;
        }).handle((f, e) -> {
            System.out.println("3333");
            int i=10/0;
            return f + 2;
        }).whenComplete((v, e) -> {
            if (e == null) {
                System.out.println("----计算结果" + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println(e.getCause());
            return null;
        });
        System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
    }
}

对计算结果进行消费

接受任务的处理结果,并消费处理,无返回结果

thenAccept

public class CompletableFutureApi2Demo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            return 1;
        }, threadPool).thenApply(f -> {
            return f + 2;
        }).thenApply(f -> {
            return f + 3;
        }).thenAccept(r -> {
            System.out.println(r);//输出6
        });
    }
}

thenRun、thenAccept、thenApply对比

  • thenRun(Runnable runnable) :任务A执行完执行B,并且不需要A的结果
  • thenAccept(Consumer action): 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
  • thenApply(Function fn): 任务A执行完执行B,B需要A的结果,同时任务B有返回值

在这里插入图片描述

public class CompletableFutureApi2Demo {
    public static void main(String[] args) {
        System.out.println(CompletableFuture.supplyAsync(() -> "result").thenRun(() -> {}).join());//null
        System.out.println(CompletableFuture.supplyAsync(() -> "result").thenAccept(r -> System.out.println(r)).join());//result null
        System.out.println(CompletableFuture.supplyAsync(() -> "result").thenApply(f -> f + 2).join());//result2
    }
}

有无Async对比

  • 如果没有传入自定义线程池,都用默认线程池ForkJoinPool
  • 传入一个线程池,如果你执行第一个任务时,传入了一个自定义线程池
    • 调用thenRun方法执行第二个任务时,则下一个任务和当前任务时共用同一个线程池

在这里插入图片描述

  • 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池

在这里插入图片描述

  • 如果第一个任务线程处理太快,系统按照优化切换原则, 直接使用main线程处理,thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,之间的区别同理。

在这里插入图片描述

源码分析

在这里插入图片描述

在这里插入图片描述

对计算速度进行选用applyToEither

  • 谁快用谁
public class CompletableFutureApiDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("A come in");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playA";
        }, threadPool);
        CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("B come in");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playB";
        }, threadPool);
        // A和B相比,谁更快用谁的结果
        CompletableFuture<String> result = playA.applyToEither(playB, f -> {
            return f + " is winner";
        });
       
        System.out.println(Thread.currentThread().getName() + "-----------winner:" + result.join());
    }
}

【输出】

A come in
B come in
main-----------winner:playB is winner

两任务组合

两个任务完成才做下一个

  • runAfterBoth: 组合两个future,不需要获取future的结果,只需两个future处理完任务后,处理任务3。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • thenAcceptBoth: 组合两个future,获取两个future任务的返回结果,然后处理任务3,没有返回值。

在这里插入图片描述

在这里插入图片描述

  • thenCombine: 组合两个future,获取两个future的返回结果,并返回当前任务的返回值

在这里插入图片描述

在这里插入图片描述

对计算结果进行合并thenCombine
  • 两个CompletableStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
  • 先完成的先等着,等待其他分支任务
public class CompletableFutureApi3Demo {
    public static void main(String[] args) {
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " 启动");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        });
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " 启动");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        });
        CompletableFuture<Integer> finalResult = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
            System.out.println("----------开始两个结果合并");
            return x + y;
        });
        System.out.println(finalResult.join());
    }
}

输出:30

在这里插入图片描述

A、B中一个完成就做下一个

在这里插入图片描述

  • 接收前面任务的返回值,当前任务有返回值

在这里插入图片描述

  • 接收前面任务的返回值,当前任务无返回值

在这里插入图片描述

  • 不接收前面任务的返回值,当前任务无返回值

在这里插入图片描述

在这里插入图片描述

多任务组合

allOf:等待所有任务完成

// 创建三个CompletableFuture对象,每个代表一个异步任务
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的图片信息");
    return "hello.jpg";
});

CompletableFuture<String> futureAtrr = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品的属性");
    return "黑色+256G";
});

CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
    System.out.println("查询商品介绍");
    return "华为";
});

// 使用CompletableFuture.allOf方法等待所有任务完成
CompletableFuture<Void> allOf = CompletableFuture.allOf(futureImg, futureAtrr, futureDesc);
// 等待所有结果完成
allOf.get();
System.out.println("main...end..." + futureImg.get() + ":" + futureAtrr.get() + ":" + futureDesc.get());

如果有很多个相似的任务,不需要像上面一样一个一个异步任务的创建,可以使用数组的方式

CompletableFuture[] futureArr = new CompletableFuture[taskList.size()];
for (int i = 0; i < taskList.size(); i++) {
    int finalI = i;
    futureArr[i] = CompletableFuture.runAsync(() -> backUpByTask(taskList.get(finalI)), executor);
}
CompletableFuture.allOf(futureArr).join();

anyOf:只要有一个任务完成

CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
// 等待任意一个任务完成,返回第一个完成的任务的返回值
anyOf.get(); 
System.out.println("main...end..." + futureImg.get() + "=>" + futureAttr.get() + "=>" + futureDesc.get());
System.out.println("main...end..." + anyOf.get());
;