第二章 CompletableFuture

第二章 CompletableFuture

2.1 Future 接口理论知识复习

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

举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会儿再去获取子任务的执行结果或变更的任务状态

image

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

2.2.1 Future 接口能干什么

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

2.2.2 Future 接口相关架构

目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)

代码实现:Runnable 接口+ Callable 接口+ Future 接口和 FutureTask 实现类

image

public class CompletableFutureDemo {

    /**
     * 运行结果:
     * ----- come in call()
     * hello callable
     */
    public static void main(String[] args) {
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();

        try {
            System.out.println(futureTask.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("----- come in call()");
        return "hello callable";
    }
}

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

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

  • 缺点:

    • get() 阻塞---一旦调用 get() 方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞

    • isDone() 轮询---轮询的方式会耗费无谓的 cpu 资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞

  • 结论:Future 对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果

2.2.4 完成一些复杂的任务

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

  • 回调通知:

    • 应对 Future 的完成时间,完成了可以告诉我,也就是我们的回调通知

    • 通过轮询的方式去判断任务是否完成这样非常占 cpu 并且代码也不优雅

  • 创建异步任务:Future+ 线程池组合

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

    • 想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值

    • 想将两个或多个异步计算合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果

  • 对计算速度选最快的:

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

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

    • 从 i 到 i++

    • Future 能干的,CompletableFuture 都能干

2.3 CompletableFuture对Future的改进

2.3.1 CompletableFuture为什么会出现

  • get() 方法在 Future 计算完成之前会一直处在阻塞状态下,阻塞的方式和异步编程的设计理念相违背

  • isDone() 方法容易耗费 cpu 资源(cpu 空转)

  • 对于真正的异步处理我们希望是可以通过传入回调函数,在 Future 结束时自动调用该回调函数,这样,我们就不用等待结果

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

2.3.2 CompletableFuture 和 CompletionStage 介绍

类架构说明:

image

接口 CompletionStage

  • 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段

  • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

类 CompletableFuture

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

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

四个静态构造方法

image

对于上述 Executor 参数说明:若没有指定,则使用默认的ForkJoinPoolcommonPool() 作为它的线程池执行异步代码,如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码

public class CompletableFutureBuildDemo {

    /**
     * 运行结果:
     * pool-1-thread-1
     * hello supplyAsync
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        /*CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            //暂停线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, executorService);
        System.out.println(completableFuture.get());*/

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            //暂停线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello supplyAsync";
        }, executorService);
        System.out.println(completableFuture.get());

        executorService.shutdown();
    }

}

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

public class CompletableFutureUseDemo {

    /**
     * 运行结果:
     * pool-1-thread-1 come in
     * main线程先去忙其他任务
     * ------ 1秒钟后出结果:8
     * ----- 异常情况:java.lang.ArithmeticException: / by zero	java.lang.ArithmeticException: / by zero
     */
    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();
            }
            System.out.println("------ 1秒钟后出结果:" + result);
            if (result > 2) {
                int i = 10 / 0;
            }
            return result;
        }, executorService).whenComplete((v, e) -> {
            if (null == e) {
                System.out.println("-------> 计算完成,更新系统 UpdateValue: " + v);
            }
        }).exceptionally(e -> {
            System.out.println("----- 异常情况:" + e.getCause() + "\t" + e.getMessage());
            return null;
        });
        System.out.println(Thread.currentThread().getName() + "线程先去忙其他任务");
        /*  try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        executorService.shutdown();
    }

    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) {
                e.printStackTrace();
            }
            System.out.println("------ 1秒钟后出结果:" + result);
            return result;
        });

        System.out.println(Thread.currentThread().getName() + "线程先去忙其他任务");
        System.out.println(completableFuture.get());
    }

}

CompletableFuture 优点:

  • 异步任务结束时,会自动回调某个对象的方法

  • 主线程设置好回调后,不用关心异步任务的执行,异步任务之间可以顺序执行

  • 异步任务出错时,会自动回调某个对象的方法

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

2.4.1 函数式编程已成为主流

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

函数式接口:

  • Runnable:无参数、无返回值

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

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

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

小结:

image

chain链式调用:

public class CompletableFutureMallDemo {

    public static void main(String[] args) {
        Student student = new Student();
        student.setId(1);
        student.setStudentName("张三");
        student.setMajor("语文");

        student.setId(2)
                .setStudentName("李四")
                .setMajor("数学");

    }


}

@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
class Student {

    private Integer id;
    private String studentName;
    private String major;

}

2.4.2 大厂业务需求说明

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

电商网站比价需求分析:

  • 需求说明:

    1. 同一款产品,同时搜索出同款产品在各大电商平台的售价

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

  • 输出返回:

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

    例如:《Mysql》 in jd price is 88.05 《Mysql》 in taobao price is 90.43

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

    1. step by step,按部就班,查完淘宝查京东,查完京东查天猫....

    2. all in,万箭齐发,一口气多线程异步任务同时查询

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

public class CompletableFutureMallDemo {

    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("taobao"),
            new NetMall("dangdang")
    );

    /**
     * step by step
     */
    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()
     */
    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)))
                ).collect(Collectors.toList())
                .stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    /**
     * 运行结果:
     * 《mysql》in jd price is 110.37
     * 《mysql》in taobao price is 109.85
     * 《mysql》in dangdang price is 110.48
     * ------costTime: 3154 毫秒
     * 《mysql》in jd price is 110.30
     * 《mysql》in taobao price is 109.02
     * 《mysql》in dangdang price is 110.33
     * ------costTime1017 毫秒
     */
    public static void main(String[] args) {
        long StartTime = System.currentTimeMillis();
        List<String> list1 = getPrice(list, "mysql");
        for (String element : list1) {
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒");

        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();
        }

        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

2.4.4 CompletableFuture 常用方法

获得结果和触发计算

  • 获取结果

    • public T get()

    • public T get(long timeout,TimeUnit unit)

    • public T join() --->和get一样的作用,只是不需要抛出异常

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

  • 主动触发计算

    • public boolean complete(T value) ---->是否打断get方法立即返回括号值
public class CompletableFutureAPIDemo {

    /**
     * 运行结果:
     * false:abc;
     */
    public static void main(String[] args) /*throws ExecutionException, InterruptedException, TimeoutException*/ {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            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) {
            e.printStackTrace();
        }
//        System.out.println(completableFuture.getNow("xxxx"));
        System.out.println(completableFuture.complete("completable value") + ":" + completableFuture.join());

    }
  • 对计算结果进行处理

    • thenApply --->计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停

    • handle --->计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步

public class CompletableFutureAPI2Demo {

    /**
     * 运行结果:
     * main线程先去忙其他任务
     * 111
     * 222
     * 333
     * result:6
     */
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("111");
            return 1;
        }, executorService).handle((v, e) -> {
            System.out.println("222");
            return v + 2;
        }).thenApply( e -> {
            System.out.println("333");
            return e + 3;
        }).whenComplete((v, e) -> {
            if (null == e) {
                System.out.println("result:" + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println("exception:" + e.getMessage());
            return null;
        });

        System.out.println(Thread.currentThread().getName() + "线程先去忙其他任务");

        executorService.shutdown();
    }

}
  • 对计算结果进行消费

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

    • thenAccept

public class CompletableFutureAPI3Demo {

    /**
     * 运行结果:
     * 6
     */
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenApply(f -> {
            return f + 2;
        }).thenApply(f -> {
            return f + 3;
        }).thenAccept(System.out::println);
    }

}
  • 对比补充

    • thenRun(Runnable runnable) :任务 A 执行完执行 B,并且不需要 A 的结果

    • thenAccept(Consumer action): 任务 A 执行完执行 B,B 需要 A 的结果,但是任务 B 没有返回值

    • thenApply(Function fn): 任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值

public class CompletableFutureAPI3Demo {

    /**
     * 运行结果:
     * null
     * result
     * null
     * result2
     */
    public static void main(String[] args) {
        /*CompletableFuture.supplyAsync(() -> {
            return 1;
        }).thenApply(f -> {
            return f + 2;
        }).thenApply(f -> {
            return f + 3;
        }).thenAccept(System.out::println);*/

        System.out.println(CompletableFuture.supplyAsync(() -> "result").thenRun(() -> {}).join());
        System.out.println(CompletableFuture.supplyAsync(() -> "result").thenAccept(r -> System.out.println(r)).join());
        System.out.println(CompletableFuture.supplyAsync(() -> "result").thenApply(f -> f + 2).join());


    }

}

CompletableFuture 和线程池说明

  • 如果没有传入自定义线程池,都用默认线程池 ForkJoinPool

  • 传入一个线程池,如果你执行第一个任务时,传入了一个自定义线程池

    • 调用 thenRun 方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池

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

  • 备注:可能是线程处理太快,系统优化切换原则, 直接使用main线程处理,thenAccept 和 thenAcceptAsync,thenApply 和 thenApplyAsync 等,之间的区别同理

public class CompletableFutureWithThreadPoolDemo {

	/**
     * 运行结果:
     * 1号任务:pool-1-thread-1
     * 2号任务:ForkJoinPool.commonPool-worker-1
     * 3号任务:ForkJoinPool.commonPool-worker-1
     * 4号任务:ForkJoinPool.commonPool-worker-1
     * null
     */
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        try {
            CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1号任务:" + Thread.currentThread().getName());
                return "abcd";
            }, executorService).thenRunAsync(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2号任务:" + Thread.currentThread().getName());
            }).thenRun(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("3号任务:" + Thread.currentThread().getName());
            }).thenRun(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("4号任务:" + Thread.currentThread().getName());
            });

            System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }

}

对计算速度进行选用

  • 谁快用谁

  • applyToEither

public class CompletableFutureFastDemo {

	/**
     * 运行结果:
     * A come in
     * B come in
     * playA is winner
     * 
     */
    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(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playA";
        }, threadPool);


        CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("B come in");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playB";
        }, threadPool);

        CompletableFuture<String> result = playA.applyToEither(playB, f -> f + " is winner");
        System.out.println(result.join());

        threadPool.shutdown();
    }

}

对计算结果进行合并

  • 两个CompletableStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理

  • 先完成的先等着,等待其他分支任务

public class CompletableFutureCombineDemo {

    /**
     * 运行结果:
     * ForkJoinPool.commonPool-worker-2 启动
     * ForkJoinPool.commonPool-worker-1 启动
     * 结果:30
     */
    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> resultFuture = completableFuture1.thenCombine(completableFuture2, Integer::sum);
        System.out.println("结果:" + resultFuture.join());

    }

}
posted @ 2026-04-13 09:16  清风含薰  阅读(4)  评论(0)    收藏  举报