三、CompletableFuture

124 阅读10分钟

一、接口理论

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

比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,

主线程就去做其他事情了,忙其它事情或者先执行完,过了一会才去获取子任务的执行结果或变更的任务状态。

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

1.future接口能干什么

Future是java5新加的一个接口,它提供了一种异步并行计算的功能

如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。

主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。

代码说话:

Runnable接口

Callable接口

Future接口和FutureTask实现类

目的:异步多线程任务执行且返回有结果,三个特点:多线/有返回/异步任务

(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)

futureTask实现Callable thread接口

他的构造方法含有 callable 和runnable 接口

futureTask 如何获取返回值

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new MyThread2());
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();;
        System.out.println(futureTask.get());
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {

    }
}

class MyThread2 implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("---- come in call()");
        return "hello callable!";
    }
}

2.Future编码实战和优缺点分析

1.优点

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

例子说明:

使用单个线程

    private static void  m1(){
        //3个任务,目前只有一个main线程来处理,请问耗时多久?
        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.MICROSECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("-----costTime"+(endTime-startTime)+"ms");
        System.out.println(Thread.currentThread().getName()+"t---end");
    }

使用多future+线程池异步创建线程

    public static void main(String[] args) {
        //3个任务,目前开启多个异步任务线程来处理,请问耗时多久?
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        long startTime = System.currentTimeMillis();

        FutureTask<String> futureTask = new FutureTask<>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task1 over";
        });
        threadPool.submit(futureTask);

        FutureTask<String> futureTask1 = new FutureTask<>(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task1 over";
        });
        threadPool.submit(futureTask1);
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("-----costTime"+(endTime-startTime)+"ms");
    }

为什么使用线程池:如果用new Thread(future);这样创建线程则会new 三次Thread。

2.缺点

A. get()阻塞
  • get 容易导致阻塞,一般建议放在程序后面,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,容易程序阻塞。
  • *假如我不愿意等待很长时间,我希望过时不候,可以自动离开。
    *

例子1:

若在业务代码前调用get(),则会阻塞get()后面的代码

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<String>(() -> {
            System.out.println(Thread.currentThread().getName() + "\t------come in");
            //暂停几秒钟线程
            TimeUnit.SECONDS.sleep(5);
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        // 不见不散 非要等到结果才会离开,不管你是否计算完成
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName()+"\t ----忙其它任务了");
    }

过时不候,给get()设置超时时间

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<String>(() -> {
            System.out.println(Thread.currentThread().getName() + "\t------come in");
            //暂停几秒钟线程
            TimeUnit.SECONDS.sleep(5);
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        // 不见不散 非要等到结果才会离开,不管你是否计算完成
//        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName()+"\t ----忙其它任务了");
        System.out.println(futureTask.get(3,TimeUnit.SECONDS));
    }
B. IsDone()轮询
  • 轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果。
  • 如果想要异步获得结果,通常都会以轮询的方式去获取结果,尽量不要阻塞
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<String>(() -> {
            System.out.println(Thread.currentThread().getName() + "\t------come in");
            //暂停几秒钟线程
            TimeUnit.SECONDS.sleep(5);
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        // 不见不散 非要等到结果才会离开,不管你是否计算完成
        System.out.println(Thread.currentThread().getName()+"\t ----忙其它任务了");
//        System.out.println(futureTask.get(3,TimeUnit.SECONDS));
        while (true){
            if(futureTask.isDone()){
                System.out.println(futureTask.get());
                break;
            }else {
                //暂停线程
                TimeUnit.MILLISECONDS.sleep(500);
                System.out.println("正在处理中");
            }
        }
    }

3.结论

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


3.想完成一些复杂的任务

1.回调通知

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

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

2.创建异步任务

Future+线程池配合

3.多个任务前后依赖可以组合处理(水煮鱼)

想将多个异步任务的计算结果组合起来 ,后一个异步任务的计算结果需要前一个异步任务的值,将两个或多个异步技术合成一个异步计算,这几个异步计算互相独立,同时后面又依赖前一个处理的结果。

ps -ef|grep tomcat 过滤

就像打游戏一样,两个线程A、B,有一个win 就通知另一个线程.

4.对计算速度最快的

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

Future能做的,CompletableFuture也能做

三、CompletableFuture对Future的改进

1.CompletableFuture 为什么出现

get()方法在Future计算完成之前会一直处在阻塞状态下,

IsDone()方法容易耗费CPU资源,

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

阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出了CompletableFuture。

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

2.CompletableFuture和CompletionStage源码分别介绍

类架构

Future只有5个方法,而CompletionStage有很多API

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

A.runAsync

  • runAsync 无 返回值
	// 线程池构造
	public static CompletableFuture<Void> runAsync(Runnable runnable,
                                                   Executor executor) {
        return asyncRunStage(screenExecutor(executor), runnable);
    }
	// 默认使用的是Fork-Joinpool线程池
    public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }

B.supplyAsync

  • supplyAsync 有 返回值
	// 线程池构造
	public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                       Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
    }
	// 默认使用的是Fork-Joinpool线程池
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }
  • 上述Excutor executor参数说明

没有指定Executor的方法,直接使用默认的Fork.JoinPool.commonPool()

作为它的线程池执行异步代码。

如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码

  • Code

无返回值

//使用自定义的线程池  
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },threadPool);
        System.out.println(future.get());
    }
//使用默认的线程池
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(future.get());
}

有返回值

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello world";
        },threadPool);
        System.out.println(completableFuture.get());

        threadPool.shutdown();;
    }

尽量不要使用new CompletableFuture 来创建,而是使用runAsync 和SupplyAsync

  • Code通用演示减少阻塞和轮询

从Java8开始引入了CompletableFuture,它是Future的功能增强版减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发送异常时,自动调用回调对象的回调方法

可以完全替代Future接口

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        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());
    }

1.主线程不要立刻结束,否则completablefuture默认使用的线程池会立刻关闭“暂停3秒钟线程

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        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;
            //v:上一步的值  e :触发的异常
        }).whenComplete((v,e)->{
            if(e==null){
                //为什么没有打印 出这句话
                //因为main线程结束了 我们的completable的默认线程池forkjoin相当于守护线程
                System.out.println("---计算完成,更新系统updateVa:"+v);
            }
        }).exceptionally(e->{
            e.printStackTrace();
            System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
            return null;
        });

        System.out.println(Thread.currentThread().getName()+"先去忙其他任务");
        //主线程不要立刻结束,否则completablefuture默认使用的线程池会立刻关闭“暂停3秒钟线程
        TimeUnit.SECONDS.sleep(3);
    }

2.也可以自定义线程池,这样主线程main结束,completablefuture 也不会立刻结束

        ExecutorService executorService = Executors.newFixedThreadPool(3);
        try {
            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;
                //v:上一步的值  e :触发的异常
            },executorService).whenComplete((v,e)->{
                if(e==null){
                    //为什么没有打印 出这句话
                    //因为main线程结束了 我们的completable的默认线程池forkjoin相当于守护线程
                    System.out.println("---计算完成,更新系统updateVa:"+v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
                return null;
            });
            System.out.println(Thread.currentThread().getName()+"先去忙其他任务");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }

出现异常

      ExecutorService executorService = Executors.newFixedThreadPool(3);
        try {
            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;
                //v:上一步的值  e :触发的异常
            },executorService).whenComplete((v,e)->{
                if(e==null){
                    //为什么没有打印 出这句话
                    //因为main线程结束了 我们的completable的默认线程池forkjoin相当于守护线程
                    System.out.println("---计算完成,更新系统updateVa:"+v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
                return null;
            });
            System.out.println(Thread.currentThread().getName()+"先去忙其他任务");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }

  • CompletableFuture 的优点
  1. 异步任务结束时,会自动回调某个对象的方法;
  2. 主线程设置好回调后,不在关心异步任务的执行,异步任务之间可以顺序执行。
  3. 异步任务出错时,会自动回调某个对象的方法。

4.案例精讲-从电商网站的比价需求说开去

A.函数式编程已经主流

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

B.说说join和get对比

get要抛出异常,join不用抛出异常

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            return "hello 1234";
        });
//      System.out.println(completableFuture.get());
        System.out.println(completableFuture.join());
    }

C. 大厂业务需求说明

需求说明

解决方案

功能版:

public class CompletableFutureMallDemo {

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

    public static List<String> getPrice(List<NetMall> list,String productName){
        // 《mysql》 int taobao  price 19.43
        return list.stream().map(netMall -> String.format(productName+"in %s price is %.2f",
                netMall.getNetMallName(),netMall.calcPrice(productName))).collect(Collectors.toList());
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        List<String> k = getPrice(list, "mysql");
        for (String s : k) {
            System.out.println(s);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("----costTime"+(endTime-startTime)+"ms");
    }
}



class NetMall{
    @Getter
    private String netMallName;

    public NetMall(String netMallName){
        this.netMallName=netMallName;
    }

    public double calcPrice(String productName){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
    }
}

性能版 (completableFuture)

    /**
     * List<NetMall>----->List<CompletableFuture>---->List<String></>
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPriceCompletableFuture(List<NetMall> list,String productName){
        // 《mysql》 int taobao  price 19.43
        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(s -> s.join())
                .collect(Collectors.toList());
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        List<String> k = getPrice(list, "mysql");
        for (String s : k) {
            System.out.println(s);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("----costTime"+(endTime-startTime)+"ms");

        long startTime2 = System.currentTimeMillis();
        k = getPriceCompletableFuture(list, "mysql");
        for (String s : k) {
            System.out.println(s);
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("----costTime"+(endTime2-startTime2)+"ms");
    }

性能版花费时间更少

5.CompletableFuture常用方法

A. 获得结果和触发技术

B.对计算结果进行处理

C.对计算结果进行消费

D.对计算速度进行选用

applyToEither:

谁快用谁,看那个线程用的最快

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> completableFutureA = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playA";
        });

        CompletableFuture<String> completableFutureB = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "playB";
        });

        CompletableFuture<String> result = completableFutureA.applyToEither(completableFutureB, f -> {
            return f + " is winner";
        });

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

E.对计算结果进行合并

  1. 两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
  2. 先完成的先等着,等待其他分支任务
  3. thenCombine
    public static void main(String[] args) {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        });

        CompletableFuture<Integer> completableFuturex = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        });

        CompletableFuture<Integer> result = completableFuturex.thenCombine(completableFuture, (x, y) -> {
            System.out.println("---合并结果");
            return x + y;
        });
        System.out.println(result.join());
    }