JUC并发编程第二章——CompletableFuture

2.1 Future接口理论知识复习

  • Future接口是jdk5开始支持的,CompletableFuture是jdk8开始支持的

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

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

public interface Future<V> {

    /**
     * 取消任务的执行
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 判断任务是否被取消
     */
    boolean isCancelled();

 	/**
     * 判断任务执行是否完毕
     */
    boolean isDone();

    /**
     * 获取异步任务的执行结果
     */
    V get() throws InterruptedException, ExecutionException;
  
    /**
     * 一定时限内获取异步任务的执行结果
     */
  	V get(long timeout, TimeUnit unit)
      	throws InterruptedException, ExecutionException, TimeoutException;
}  

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

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

2.2.1 Future接口能干什么

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

2.2.2 Future接口相关架构

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

FutureTask开启异步任务代码实现

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        long start = System.currentTimeMillis();

        FutureTask<String> task1 = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                TimeUnit.MILLISECONDS.sleep(500);
                return "task1被提交";
            }
        });

        FutureTask<String> task2 = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                TimeUnit.MILLISECONDS.sleep(500);
                return "task2被提交";
            }
        });

        FutureTask<String> task3 = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                TimeUnit.MILLISECONDS.sleep(500);
                return "task3被提交";
            }
        });

        executorService.submit(task1);
        executorService.submit(task2);
        executorService.submit(task3);
        System.out.println(task1.get());
        System.out.println(task2.get());
        System.out.println(task3.get());

        long end = System.currentTimeMillis();
        System.out.println(end-start);

        executorService.shutdown();
    }

Future编码实战和优缺点分析

优点

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

缺点1

get方法容易阻塞,一般放在程序后面。一旦调用get()方法,就会非要等到结果才会离开,不管是否计算完成,容器程序阻塞。
假如不愿意等待很长时间,希望过时不候,可以自动离开,使用get(long timeout, TimeUnit unit)方法,超时未获取到结果会抛TimeoutException异常,程序可以通过抓取这个异常避免阻塞。

例:

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) {
                e.printStackTrace();
            }
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();

        System.out.println(Thread.currentThread().getName() + "\t --- 忙其它任务了");

//        System.out.println(futureTask.get()); // 一旦调用get()方法,就会非要等到结果才会离开,不管是否计算完成,容器程序阻塞
        System.out.println(futureTask.get(3, TimeUnit.SECONDS)); // 不愿意等待很长时间,我希望过时不候,可以自动离开,这里会抛TimeoutException异常,程序可以通过抓取这个异常避免阻塞
    }

}

输出结果:
main	 --- 忙其它任务了
t1	 ---come in
Exception in thread "main" java.util.concurrent.TimeoutException
	at java.util.concurrent.FutureTask.get(FutureTask.java:205)
	at com.fastech.juc.completablefuture.FutureAPIDemo.main(FutureAPIDemo.java:32)

缺点2

  1. 轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果
  2. 如果想要异步获取结果,通常都会以轮询地方式去获取结果,尽量不要阻塞
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) {
                e.printStackTrace();
            }
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();

        System.out.println(Thread.currentThread().getName() + "\t --- 忙其它任务了");

        while (true) {
            if (futureTask.isDone()) {
                System.out.println(futureTask.get());
                break;
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("正在处理中。。。");
            }
        }
    }

}

输出结果:
main	 --- 忙其它任务了
t1	 ---come in
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
task over

2.2.3想完成一些复杂的任务


        对于简单的业务场景使用Future完全OK,但是对于高并发复杂的业务场景,需要不停的轮询或者容易导致阻塞,或多或少都有一些潜在的风险,另外,我们更希望Future这个异步任务获取返回值功能更加强大!具体需求如下

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

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

2.创建异步任务
尽量把复杂的交给异步任务,主干还是专注自己的业务逻辑,不牵扯在一起,可以使用前面演示的Future+线程池配合实现

3.多个任务前后依赖可以组合处理
想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值

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

例如:ps -ef|grep tomcat,这个linux系统的命令就是在ps -ef结果集中找到带有tomcat的结果集,先要找到ps-ef的结果集,再从中找到带有tomcat的结果集。

4.对计算速度选最快
当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果。

例如:两个人比赛玩消消乐,谁先完成就显示谁win

2.3 CompletableFuture对Future的改进

2.3.1 CompletableFuture为什么会出现

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

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

2.3.2 CompletableFuture和CompletionStage介绍

类架构说明

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

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

默认无参构造(不推荐使用)

/**
 * 创建一个新的不完整的CompletableFuture
 */
public CompletableFuture() {
}

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

runAsync无返回值

public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}

public static CompletableFuture<Void> runAsync(Runnable runnable,
                                               Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
}

这里演示一种情况:

public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        //四个核心方法创建一个异步任务

        //1.runAsync
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("-----------------");
            System.out.println(Thread.currentThread().getName());
        });

//        CompletableFuture.runAsync(() -> {
//            System.out.println("*******************");
//            System.out.println(Thread.currentThread().getName());
//        }, executorService);

        //为了防止main线程太快结束,而异步任务没有打印结果,因此停1s
//        TimeUnit.SECONDS.sleep(1);
    }

        这段代码的运行结果什么都不会打印,那么可能就会有疑问了,这里不是开启了一个异步任务,那打印结果呢?这是因为,默认情况不加任何线程参数,使用ForkJoinPool线程池中的线程,会开启一个守护线程。而我们知道守护线程的概念,由于main没有阻塞,main先执行完毕,就会导致守护线程还没来得及打印结果,所有线程就关闭了!

        如果把使用线程池的注释放开,或者让main线程休眠1s,就能看到打印结果了。

supplyAsync有返回值

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                   Executor executor) {
    return asyncSupplyStage(screenExecutor(executor), supplier);
}

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

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

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) -> {
            if (e == null) {
                System.out.println("计算完成 更新系统" + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
            return null;
        }).whenComplete((v, e) -> {
            System.out.println(Thread.currentThread().getName()+" 2");
        }).whenComplete((v, e) -> {
            System.out.println(Thread.currentThread().getName()+" 3");
        });
        System.out.println(Thread.currentThread().getName() + "先去完成其他任务");
        executorService.shutdown();
    }
}
/*
没有异常的情况:
    pool-1-thread-1---come in
    main先去完成其他任务
    ----------1秒钟后出结果2
    计算完成 更新系统2
    pool-1-thread-1 2
    pool-1-thread-1 3
*/
/*
    出现异常的情况
    pool-1-thread-1---come in
main先去完成其他任务
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1606)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.ArithmeticException: / by zero
	at MyRunnable.lambda$main$0(MyRunnable.java:111)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
	... 3 more
异常情况:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero
pool-1-thread-1 2
pool-1-thread-1 3
*/

CompletableFuture优点:

  • 异步任务结束时,会自动回调某个对象的方法 (就是whenCompelete中的业务逻辑)
  • 主线程设置好回调后,不用关心异步任务的执行,异步任务之间可以顺序执行,而且不影响后续异步任务的执行。
  • 异步任务出错时,会自动回调某个对象的方法。

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

函数式接口

  • Runnable:无参数、无返回值
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • Function:功能型函数式接口,接受一个参数,并且有返回值
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
  • Consumer:消费型函数式接口,接受一个参数,没有返回值
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
  • BiConsumer:接受两个参数,没有返回值
@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
}
  • Supplier:供给型函数式接口,没有参数,有返回值
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

小结:

chain链式调用:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

public class CompletableFutureMallDemo {

    public static void main(String[] args) {
        // 老写法
        Student student1 = new Student();
        student1.setId(1);
        student1.setStudentName("zhangsan");
        student1.setMajor("java");

        // chain链式调用
        Student student2 = new Student();
        student2.setId(2).setStudentName("lisi").setMajor("python");
    }

}

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

join方法和get方法对比

join和get区别在于:join在编译期间不会报检查性异常,运行时该报错报错,而get在编译期间报检查性异常

import java.util.concurrent.CompletableFuture;

public class CompletableFutureMallDemo2 {

    public static void main(String[] args) /*throws ExecutionException, InterruptedException*/ {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "hello 1234");
//        System.out.println(completableFuture.get());//用get方法必须抛异常
        System.out.println(completableFuture.join());
    }

}

输出结果:
hello 1234

三:电商网站比价样例

需求说明

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

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

例如:

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

解决方案

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

step by step,按部就班,查完淘宝查京东,查完京东查天猫…串行查询
all in,万箭齐发,一口气多线程异步任务同时查询,并行查询
代码样例:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class CompletableFutureMallCase {

    static List<NetMall> list = Arrays.asList(new NetMall("jd"), 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()//转成stream流
                .map(//做映射,把原来list集合中的每个NetMall类型的元素转为指定格式的String类型元素
                        netMall -> String.format("《" + productName + "》" + "in %s price is %.2f",
                                netMall.getNetMallName(),
                                netMall.calcPrice(productName)
                        )
                        // 使用collect方法收集Stream流中处理后的结果,将其转换为一个新的List集合,最终返回结果是包含了每个元素信息的List集合。
                ).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表示List中的每一个元素,对每个元素进行如下操作:
                 * 使用CompletableFuture.supplyAsync方法创建一个CompletableFuture对象,异步执行Lambda表达式中的操作。
                 * Lambda表达式中使用String.format方法格式化输出字符串,其中包含了productName和netMall对象的信息,并返回新的字符串。
                 * 返回的结果是一个Stream流中包含CompletableFuture对象的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<CompletableFuture<String>>
                //使用map操作对流中的每一个CompletableFuture对象调用join方法,获取异步计算的结果。
                .map(CompletableFuture::join) // Stream<String>
                .collect(Collectors.toList()); // List<String>
    }

    public static void main(String[] args) {

        /**
         * 采用step by setp方式查询
         * 《mysql》in jd price is 110.11
         * 《mysql》in taobao price is 109.32
         * 《mysql》in dangdang price is 109.24
         * ------costTime: 3094 毫秒
         */
        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) + " 毫秒");

        System.out.println("-----------------------------------");

        /**
         * 采用 all in三个异步线程方式查询
         * 《mysql》in jd price is 109.71
         * 《mysql》in taobao price is 110.69
         * 《mysql》in dangdang price is 109.28
         * ------costTime: 1009 毫秒
         */
        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.0, 1.0) 之间的随机双精度浮点数的方法。
         * 在多线程环境下使用ThreadLocalRandom可以避免因为多个线程抢占同一个随机数生成器实例而导致性能下降的问题。
         */
        //这里就是大概模拟一个值作为当前当前产品的价钱,产品首字母的ASCII值加一个随机值
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

如果用Stream流不好理解,也可以用传统写法

public class CompletableFutureDemo {

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

    public static void main(String[] args) {
        long l = System.currentTimeMillis();
        getPrice(list, "mysql");
        long l1 = System.currentTimeMillis();
        System.out.println(l1 - l);
        getPriceByCompletableFuture(list, "mysql");
        System.out.println(System.currentTimeMillis() - l1);
    }

    public static List<String> getPrice(List<NetMall> list, String productName) {
        ArrayList<String> res = new ArrayList<>();

        for (NetMall netMall : list) {
            String s = String.format("《" + productName + "》" + "in %s price is %.2f",
                    netMall.getNetMallName(),
                    netMall.calcPrice(productName));
            res.add(s);
        }

        return res;
    }

    public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName) {
        ArrayList<String> res = new ArrayList<>();
        ArrayList<CompletableFuture> futureArrayList = new ArrayList<>();
        for (NetMall netMall : list) {
            CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
                return String.format("《" + productName + "》" + "in %s price is %.2f",
                        netMall.getNetMallName(),
                        netMall.calcPrice(productName));
            });
            futureArrayList.add(future);
        }

        for (CompletableFuture future : futureArrayList) {
            String join = (String) future.join();
            res.add(join);
        }

        return res;
    }

}

@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.0, 1.0) 之间的随机双精度浮点数的方法。
         * 在多线程环境下使用ThreadLocalRandom可以避免因为多个线程抢占同一个随机数生成器实例而导致性能下降的问题。
         */
        //这里就是大概模拟一个值作为当前当前产品的价钱,产品首字母的ASCII值加一个随机值
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

四:CompletableFuture常用方法

a.获得结果(五个方法)和触发计算(两个方法)

1.获取结果


        public T get() —>阻塞当前线程,直到获取CompletableFuture返回值为止
        public T get(long timeout,TimeUnit unit) —>阻塞当前线程指定时间,超时抛出TimeoutException
        public T join() —>和get一样的作用,只是不需要抛出异常
        public T getNow(T valueIfAbsent) —>计算完成就返回正常值,否则返回备胎值(设定的valueIfAbsent),立即获取结果不阻塞
主动触发计算
        public boolean complete(T value) ---->是否打断get方法立即返回括号值

Code

package com.bilibili.juc.completablefuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CompletableFutureAPIDemo {

    public static void main(String[] args) /*throws ExecutionException, InterruptedException, TimeoutException*/ {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } 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());
        System.out.println(completableFuture.getNow("xxx"));
      	System.out.println(completableFuture.complete("completeValue") + "\t" + completableFuture.join());
    }

}

输出结果:
xxx
true	completeValue
2.触发计算

1.thenApply

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

Code


    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("--------步骤一--------");
            return 1;
        }, threadPool).thenApply(f -> {
          	int i = 10 / 0;
            System.out.println("--------步骤二--------");
            return f + 2;
        }).thenApply(f -> {
            System.out.println("--------步骤三--------");
            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();
    }


输出结果:
main--------主线程先去忙其它任务--------
--------步骤一--------
java.lang.ArithmeticException: / by zero
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:618)
	at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:591)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1609)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ArithmeticException: / by zero
	at com.bilibili.juc.completablefuture.CompletableFutureAPI2Demo.lambda$main$1(CompletableFutureAPI2Demo.java:27)
	at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616)
	... 7 more

2.handle

计算结果存在依赖关系,这两个线程串行化 ---->有异常也可以往下走一步,根据带的异常参数可以进一步处理

Code

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("--------步骤一--------");
            return 1;
        }, threadPool).handle((f, e) -> {
            int i = 10 / 0;
            System.out.println("--------步骤二--------");
            return f + 2;
        }).handle((f, e) -> {
            System.out.println("--------步骤三--------");
            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();
    }
输出结果:
main--------主线程先去忙其它任务--------
--------步骤一--------
--------步骤三--------
java.lang.NullPointerException
java.util.concurrent.CompletionException: java.lang.NullPointerException
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:838)
	at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:811)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1609)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException
	at com.bilibili.juc.completablefuture.CompletableFutureAPI3Demo.lambda$main$2(CompletableFutureAPI3Demo.java:32)
	at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:836)
	... 7 more

        注:正常情况下thenApply和handle方法执行结果相同,某一环出现异常时,thenApply直接报错,不往后面走。而handle方法可以往下继续执行,并且可以把当前异常带到后面处理。

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

Code

 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
    }

b.CompletableFuture和线程池说明

以thenRun和thenRunAsync为例,有什么区别?

Code

     public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        try {
            CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
                return "abcd";
            }, threadPool).thenRunAsync(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
            }).thenRun(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
            }).thenRun(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
            });
            System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }

输出结果:
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 threadPool = Executors.newFixedThreadPool(5);

        try {
            CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
                return "abcd";
            }, threadPool).thenRun(() -> {//注意这里用的是thenRun,没有另启一个异步任务,所以和上一个线程用同一个线程
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
            }).thenRun(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
            }).thenRun(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
            });
            System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }


输出结果:
1号任务	pool-1-thread-1
2号任务	pool-1-thread-1
3号任务	pool-1-thread-1
4号任务	pool-1-thread-1
null

小总结
1.如果没有传入自定义线程池,都用默认线程池ForkJoinPool
2.传入了一个自定义线程池。如果你执行第一个任务时,传入了一个自定义线程池
        调用thenRun方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池
        调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池
3.如果第一个任务耗时非常低,可能是线程处理太快,系统优化切换原则, 调用thenRun方法执行第二、三…n个任务时,直接使用main线程处理
4.thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,之间的区别同理。

源码分析

//thenRunAsync方法会调用uniRunStage,并且传入一个asyncPool
public CompletableFuture<Void> thenRunAsync(Runnable action) {
        return uniRunStage(asyncPool, action);
}


// ForkJoinPool.getCommonPoolParallelism()获取系统CPU核数,现在CPU核数肯定都大于1,所以thenRunAsync()默认使用ForkJoinPool线程池
private static final boolean useCommonPool =
        (ForkJoinPool.getCommonPoolParallelism() > 1);//如果系统cpu核数大于1,useCommonPool为true

//asyncPool线程池的赋值
private static final Executor asyncPool = useCommonPool ?//如果这个useCommonPool为true,就使用ForkJoinPool线程池中的线程
  ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

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

c.applyToEither

比较两个线程谁快返回谁

     public static void main(String[] args) {
        CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
            System.out.println("playA come in");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playA";
        });
        CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
            System.out.println("playB come in");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playB";
        });
        CompletableFuture<String> result = playA.applyToEither(playB, f -> f + " is winner");
        System.out.println(Thread.currentThread().getName() + "线程执行结束\t" + "--------: " + result.join());
    }

输出结果:
playA come in
playB come in
main线程执行结束	--------: playA is winner
d.对计算结果进行合并thenCombine

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

注意:先完成的先等着,等待其他分支任务

      public static void main(String[] args) {
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t --------启动");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        });

        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t --------启动");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        });

        CompletableFuture<Integer> result = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
            System.out.println("--------开始两个结果合并--------");
            return x + y;
        });

        System.out.println(result.join());
    }

输出结果:
ForkJoinPool.commonPool-worker-1	 --------启动
ForkJoinPool.commonPool-worker-2	 --------启动
--------开始两个结果合并--------
30

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值