CompletableFuture介绍

CompletableFuture

Future接口理论知识复习

  • Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果,取消任务,判断任务是否被取消,判断任务是否完毕等
  • 比如主线程让一个子线程取执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程去做其他事情了,过了一会才去获取子任务的执行结果或变更的任务状态
  • 一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力地复杂业务

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

Future接口能干什么
  • Future是Java5新加的一个接口,提供了一种一部并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果
  • Runnable接口
  • Callable接口
    • Runnable重新run方法,Callable重写call方法;Runnable无返回值,Callable有返回值;Runnable不能抛异常,Callable能抛异常
    • callable和runnable都可以应用于executors。而thread类只支持runnable及其子类.
public static void main(String[] args) throws Exception{
		//1 创建一个线程池
		//调用Executors类的静态方法
		ExecutorService service = Executors.newFixedThreadPool(10);
		//2提交runnable对象
		service.submit(new Runnable() {
			@Override
			public void run() {
			}
		});
		//3 提交callable对象
		service.submit(new Callable<String>() {
			@Override
			public String call() throws Exception {
				return null;
			}
		});
		//4 关闭线程池
		service.shutdown();
	}

  • Future接口和FutureTask实现类

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

thread支持runnable(多线程),runable的实现接口runableFuture以及子类FutureTask支持异步任务,但是FutureTask支持构造注入Callable;符合上面的三个条件

本源的Future接口相关架构

在这里插入图片描述

Future编码实战和优缺点分析
public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>( () -> {
            return "hello callable";
        });

        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
优点
  • future+线程池异步多线程任务配置,能显著提高程序的执行效率
缺点
  • get()阻塞,如果get()位置不正确会阻塞程序的运行 解决办法:get(3,TimeUnit.SECONDS):3秒钟后如果未执行完毕,抛出异常
  • isDone轮询会导致CPU空转:轮询的方式会耗费cpu资源,而且也不见得能及时地得到计算结果.如果想要异步获取结果,通常都会以轮询的方式去获取结果,尽量不要阻塞;
while (true){
            if (futureTask.isDone()) {
                System.out.println(futureTask.get());
                break;
            }else {
                TimeUnit.SECONDS.sleep(1);
            }
        } 

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

想完成一些复杂的任务
对于简单的业务场景使用Future完全ok
回调通知
  • 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知,通过轮询的方式去判断任务是否完成这样非常占CPU并且代码也不优雅
创建异步任务
  • Future+线程池配合
多个任务前后依赖可以组合处理
  • 想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值,将两个或者多个异步任务合成一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的
对计算速度选最快
  • 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果

总结:使用Future之前提供的那点API处理起来不够优雅,CompletableFuture以声明式的方式优雅的处理这些需求,Future能干的,CompletableFuture都能干.

ComplatableFuture对Future的改进

ComplatableFuture为什么出现
  • get()方法在Future计算完成之前会一直处于阻塞状态
  • isDone()方法容易耗费CPU资源
  • 对于真正的异步任务我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果.
  • 阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源.因此设计出ComplatableFuture
    • ComplatableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方
ComplatableFuture和CompletionStage源码分别介绍
接口CompletionStage
  • CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
    • 一个阶段的计算执行可以是一个Function,Consumer或者Runnable.比如:stage.thenApply(x ->square(x)).thenAccept(x -> sout(x)).thenRun(()->sout)
    • 一个阶段的执行可能是单个阶段的完成触发,也可能是多个阶段一起触发
  • 代表异步计算过程中的某一个阶段,一个阶段完成可能会触发另外一个阶段,
接口ComplatableFuture
  • 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合ComplatableFuture的方式
  • 他可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在完成计算后触发一些函数式或执行某些动作
  • 它实现了Future和CompletionStage接口
核心的四个静态方法,来创建一个异步任务
runAsync无返回值
//返回一个新的CompletableFuture,在{@link ForkJoinPool#commonPool()}中运行的任务运行给定操作后,该任务将异步完成。
//@param runnable the action to run before completing the returned CompletableFuture
//@return the new CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable)

//返回一个新的CompletableFuture,该Future由运行给定操作后在给定执行器中运行的任务异步完成。
//@param runnable在完成返回的CompletableFuture之前运行操作
//@param executor用于异步执行的执行器
//@返回新的CompletableFuture
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)
上述Executor executor参数说明
  • 没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为他的线程池执行异步代码
  • 如果执行线程池,则使用我们自定义的或者特别的线程池执行异步代码
Code

在这里插入图片描述

Code之通用演示,减少阻塞和轮询
  • 从java8开始引入了CompletionFuture,他是Future的功能增强版,减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
  • 主线程不要立刻结束,否则CompletionFuture默认使用的线程池会立刻关闭;可以自定义线程池,但是要记得关闭
CompletionFuture的优点
  • 异步任务结束时,会自动调用某个对象的方法;
  • 主线程设置好会掉之后,不再关心异步任务的执行,异步任务之间可以顺序执行
  • 异步任务出错时,会自动回调某个对象的方法

案例精讲

函数式编程成为主流
  • Java8函数式编程用过吗
  • Lambda表达式+Stream流式调用+Chain链式调用+Java8函数式编程
    • Chain链式调用:需要在实体类上面添加@Accessors(chain = true)此注解属于lombok的注解,需要引入相关jar包
先说说join和get对比
  • 都可以从CompletableFuture中获取到返回值
  • 区别:编译时是否报错
需求说明
  • 同一款产品,同时搜索出同款产品在各大电商平台的售价
  • 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
  • 输出返回:希望是同款产品在不同地方的价格清单列表,返回一个List
    • <> in jd price is 88.05
    • <> in dangdang price is 88.05
    • <> in taobao price is 88.05
  • 解决方案:比对同一个产品在各大平台的价格,要求获得一个清单列表
    • 方案1:查完京东查淘宝…
    • 方案2:万箭齐发,一口气多线程异步任务同时查询
public class CompletableFutureMall {
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao")
    );

    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.getNetMallNmame(),
                netMall.calPrice(productName))))
                .collect(Collectors.toList())
                .stream()
                .map(s -> s.join())
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        getPriceByCompletableFuture(list, "mysql");
        long end = System.currentTimeMillis();
        System.out.println("============>" + (end - start));
    }


}

class NetMall{
    @Getter
    private String netMallNmame;
    public NetMall(String netMallNmame){
        this.netMallNmame = netMallNmame;
    }

    public double calPrice(String productName){
        try {
            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

CompeletableFuture常用方法

获得结果和触发计算
  • 获取结果
    • get()
    • get(long timeout ,TimeUnit unit)
    • join()
    • getNow(T valueIfAbsent):如果计算完成返回计算的结果值或者抛出的异常,如果没有计算完成,则返回给的默认值
      • 没有计算完成的情况下,给我一个替代结果
      • 计算完,返回计算完成后的结果(或者是异常)
      • 没计算完,完成设定的valueIfAbsent值
  • 主动触发计算
    • public boolean complete(T value):是否打断get方法立即返回括号值
对计算结果进行处理
thenApply
  • 计算结果存在依赖关系,这两个线程串行化
  • code
CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("====> 111" );
            return 1;
        }).thenApply( item1 -> {
            int i = 10/0;
            System.out.println("====> 222");
            return item1 + "12";
        }).thenApply( item2 -> {
            System.out.println("====> 333");
            return Integer.valueOf(item2);
        }).whenComplete( (value, exception) -> {
            //这一步要对exception做判断
            if (exception != null){
                System.out.println("=======>whenComplete");
                System.out.println(exception);
            }else {
                //如果不对exception做判断可能会影响到值得使用,因为获取到的值为null;
                System.out.println("计算结果为:==>" + value);
            }
        }).exceptionally( e -> {
            System.out.println("=======>exceptionally");
            System.out.println(e);
            return null;
        });


//打印结果:执行完第一个,就直接执行whenComplete和exceptionally
====> 111
=======>whenComplete
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
=======>exceptionally
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
============>1015
  • 异常相关:由于存在依赖关系(当前错,不走下一步),当前步骤有异常的化就直接叫停
handle
  • 计算结果存在依赖关系,这两个线程串行化
  • code
CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("====> 111" );
            return 1;
        }).handle( (item1, e) -> {
            int i = 10/0;
            System.out.println("====> 222");
            return item1 + "12";
        }).handle( (item2, e) -> {
            System.out.println("====> 333");
            return Integer.valueOf(item2);
        }).whenComplete( (value, exception) -> {
            //这一步要对exception做判断
            if (exception != null){
                System.out.println("=======>whenComplete");
                System.out.println(exception);
            }else {
                //如果不对exception做判断可能会影响到值得使用,因为获取到的值为null;
                System.out.println("计算结果为:==>" + value);
            }
        }).exceptionally( e -> {
            System.out.println("=======>exceptionally");
            System.out.println(e);
            return null;
        });

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//打印结果:执行完第一个虽然第二个报错,但是仍然执行完了第三个,然后执行whenComplete和exceptionally
====> 111
====> 333
=======>whenComplete
java.util.concurrent.CompletionException: java.lang.NumberFormatException: null
=======>exceptionally
java.util.concurrent.CompletionException: java.lang.NumberFormatException: null
============>1015
  • 异常相关:有异常也可以往下一步走,根据带的参数可以进一步处理
总结
  • exceptionally相当于try/catch
  • whenComplete+handle相当于try/finally
对计算结果进行消费
接收人任务的处理结果,并消费处理,无返回结果
  • 消费性接口
thenAccept
CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("====> 111" );
            return 1;
        }).thenApply( item1 -> {
            int i = 10/0;
            System.out.println("====> 222");
            return item1 + "12";
        }).thenApply( item2 -> {
            System.out.println("====> 333");
            return Integer.valueOf(item2);
        }).thenAccept((integer -> {
            System.out.println(integer);
            //如果加了whenComplete注意thenAccept是一个消费性接口.并不会有返回值,所以value一定为null
        })).whenComplete( (value, exception) -> {
            //这一步要对exception做判断
            if (exception != null){
                System.out.println("=======>whenComplete");
                System.out.println(exception);
            }else {
                System.out.println("计算结果为:==>" + value);
            }
            //如果有异常,就会走下面这个方法
        }).exceptionally( e -> {
            System.out.println("=======>exceptionally");
            System.out.println(e);
            return null;
        });
对比补充
  • Code之任务之间的顺序执行
    • thenRun
      • 任务A执行完执行B,并且B不需要A得结果
    • thenAccept
      • 任务A执行完执行B,B需要A得结果,但是任务B无返回值
    • thenApply
      • 任务A执行完执行B,B需要A得结果,同时任务B有返回值
    • Code
CompletableFuture和线程池说明
  • thenRun和thenRunAsync为例,有什么区别??
    • 没有传入自定义线程池,都用默认的线程池ForkJoinPool
    • 传入了一个自定义线程池,如果你执行完第一个任务的时候,传入了一个自定义线程池
      • 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务公用一个线程池
      • 调用thenRunAsync方法执行第二个任务时,第一个任务公用自定义得线程池,第二个任务使用的是ForkJoin线程池
对计算速度进行选用
applyToEither(谁快用谁)

在这里插入图片描述

对计算结果进行合并
  • 两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCompine来处理,先完成的先等着,等待其他分支任务
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值