• CompletableFuture异步编程Api使用详解


    Java 8 引入了很多的新特性,其中就包含了 CompletableFuture 类的引入,它允许我们通过在与主应用程序线程不同的线程上(也就是异步)运行任务,并向主线程通知任务的进度、完成或失败,来编写非阻塞代码。

    Future的局限性

    从本质上说,Future表示一个异步计算的结果。它提供了isDone()来检测计算是否已经完成,并且在计算结束后,可以通过get()方法来获取计算结果。在异步计算中,Future确实是个非常优秀的接口。但是,它的本身也确实存在着许多限制:并发执行多任务:Future只提供了get()方法来获取结果,并且是阻塞的。所以,除了等待你别无他法;无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在Future中这是无能为力的;没有异常处理:Future接口中没有关于异常处理的方法;

    CompleteableFuture

    简单的任务,用Future获取结果还好,但我们并行提交的多个异步任务,往往并不是独立的,很多时候业务逻辑处理存在串行[依赖]、并行、聚合的关系。如果要我们手动用 Fueture 实现,是非常麻烦的。

    JDK1.8 才新加入的一个实现类 CompletableFuture,实现了 Future,CompletionStage两个接口。实现了 Future 接口,意味着可以像以前一样通过阻塞或者轮询的方式获得结果。

    CompletableFuture是Future接口的扩展和增强。CompletableFuture实现了Future接口,并在此基础上进行了丰富地扩展,完美地弥补了Future上述的种种问题。更为重要的是,CompletableFuture实现了对任务的编排能力。借助这项能力,我们可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。

    为什么要引入 CompletableFuture

    Java 的 1.5 版本引入了 Future,你可以把它简单的理解为运算结果的占位符,它提供了两个方法来获取运算结果。
    get():调用该方法线程将会无限期等待运算结果。get(long timeout, TimeUnit unit):调用该方法线程将仅在指定时间 timeout 内等待结果,如果等待超时就会抛出 TimeoutException 异常。

    Future 可以使用 Runnable 或 Callable 实例来完成提交的任务,通过其源码可以看出,它存在如下几个问题: 阻塞 调用 get() 方法会一直阻塞,直到等待直到计算完成,它没有提供任何方法可以在完成时通知,同时也不具有附加回调函数的功能。链式调用和结果聚合处理 在很多时候我们想链接多个 Future 来完成耗时较长的计算,此时需要合并结果并将结果发送到另一个任务中,该接口很难完成这种处理。异常处理 Future 没有提供任何异常处理的方式。

    以上这些问题在 CompletableFuture 中都已经解决了,接下来让我们看看如何去使用 CompletableFuture。

    常用方法

    依赖关系

    thenApply():把前面任务的执行结果,交给后面的Function
    thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回
    
    • 1
    • 2

    and集合关系

    thenCombine():合并任务,有返回值
    thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
    runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)
    
    • 1
    • 2
    • 3

    or聚合关系

    applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
    acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
    runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)
    
    • 1
    • 2
    • 3

    并行执行

    allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
    anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
    
    • 1
    • 2

    结果处理

    whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
    exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成
    
    • 1
    • 2
    异步操作

    CompletableFuture提供了四个静态方法来创建一个异步操作:

    public static CompletableFuture runAsync(Runnable runnable)
    public static CompletableFuture runAsync(Runnable runnable, Executor executor)
    public static  CompletableFuture supplyAsync(Supplier supplier)
    public static  CompletableFuture supplyAsync(Supplier supplier, Executor executor)
    
    • 1
    • 2
    • 3
    • 4

    这方法的区别:
    1)runAsync() 以Runnable函数式接口类型为参数,没有返回结果。
    2)supplyAsync() 以Supplier函数式接口类型为参数,返回结果类型为U;

    异步操作
    Runnable runnable = () -> System.out.println("无返回结果异步任务");
    CompletableFuture.runAsync(runnable);
    
    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        System.out.println("有返回值的异步任务");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello World";
    });
    String result = future.get();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    获取结果(join&get)

    join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)
    结果处理

    当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

    public CompletableFuture whenComplete(BiConsumer action)
    public CompletableFuture whenCompleteAsync(BiConsumer action)
    public CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor)
    
    • 1
    • 2
    • 3

    Action的类型是BiConsumer,它可以处理正常的计算结果,或者异常情况。方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
    这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。

    应用场景

    描述依赖关系:
    thenApply() 把前面异步任务的结果,交给后面的FunctionthenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回
    
    • 1

    描述and聚合关系:

    thenCombine:任务合并,有返回值thenAccepetBoth:两个任务执行完成后,将结果交给thenAccepetBoth消耗,无返回值。runAfterBoth:两个任务都执行完成后,执行下一步操作(Runnable)。
    
    • 1
    描述or聚合关系:
    applyToEither:两个任务谁执行的快,就使用那一个结果,有返回值。acceptEither: 两个任务谁执行的快,就消耗那一个结果,无返回值。runAfterEither: 任意一个任务执行完成,进行下一步操作(Runnable)。
    
    • 1
    并行执行:

    CompletableFuture类自己也提供了anyOf()和allOf()用于支持多个CompletableFuture并行执行

    总结 CompletableFuture 几个关键点:

    1、计算可以由 Future ,Consumer 或者 Runnable 接口中的 apply,accept或者 run 等方法表示。

    2、计算的执行主要有以下

    a. 默认执行
    
    b. 使用默认的 CompletionStage 的异步执行提供者异步执行。这些方法名使用 someActionAsync 这种格式表示。
    
    c. 使用 Executor 提供者异步执行。这些方法同样也是 someActionAsync 这 种格式,但是会增加一个 Executor 参数。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    CompletableFuture的API非常丰富,不用全部掌握,大概了解有哪些功能,使用时会查API就行。
    在这里插入图片描述在这里插入图片描述

    一个实际的例子

    public SkuItemVo item(Long skuId) {
        SkuItemVo skuItemVo = new SkuItemVo();
     
        //1、sku详细信息 sku_info
        SkuInfoEntity skuInfo = getById(skuId);
        skuItemVo.setInfo(skuInfo);
     
        //2、sku 图片信息 sku_img
        List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
        skuItemVo.setImages(images);
     
        //3、spu 销售属性组合
        List<SkuItemSaleAttrVo> saleAttr = skuSaleAttrValueService.getSaleAttrBySpuId(skuInfo.getSpuId());
        skuItemVo.setSaleAttr(saleAttr);
     
        //4、spu 的介绍
        SpuInfoDescEntity spuInfoDesc = spuInfoDescService.getById(skuInfo.getSpuId());
        skuItemVo.setDesc(spuInfoDesc);
     
        //5、spu 规格参数信息
        List<SpuItemAttrGroupVo> groupAttrs = attrGroupService.getAttrGroupWithAttrsBySpuId(skuInfo.getSpuId(),skuInfo.getCatalogId());
        skuItemVo.setGroupAttrs(groupAttrs);
     
        return skuItemVo;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    使用CompletableFuture异步编排后

    private SkuItemVo item(Long skuId) {
        SkuItemVo skuItemVo = new SkuItemVo();
     
        /**
         * 3、4、5需要依赖1的运行结果,需要返回skuInfo后从中获取spuId和catalogId
         * 而2不需要依赖1的运行结果
         */
     
        //1、sku详细信息 sku_info
        CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
                SkuInfoEntity skuInfo = getById(skuId);
                skuItemVo.setInfo(skuInfo);
                return skuInfo;
        }, executor);
     
        //2、sku 图片信息 sku_img  2不需要等待上边1的执行结果
        CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
                List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
                skuItemVo.setImages(images);
        }, executor);
     
        //下边的3、4、5都需要上边1的执行结果
        //所以下边的3、4、5都是基于上边1的执行结果 infoFuture 开始的
        //都是以infoFuture.thenAcceptAsync(skuInfo -> {})开始的
        CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync(skuInfo -> {
                //3、spu 销售属性组合  3
                List<SkuItemSaleAttrVo> saleAttr = skuSaleAttrValueService.getSaleAttrBySpuId(skuInfo.getSpuId());
                skuItemVo.setSaleAttr(saleAttr);
                System.out.println(saleAttr);
        }, executor);
     
        CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync(skuInfo -> {
                //4、spu 的介绍
                SpuInfoDescEntity spuInfoDesc = spuInfoDescService.getById(skuInfo.getSpuId());
                skuItemVo.setDesc(spuInfoDesc);
        }, executor);
     
        CompletableFuture<Void> attrGroupFuture = infoFuture.thenAcceptAsync(skuInfo -> {
                //5、spu 规格参数信息
                List<SpuItemAttrGroupVo> groupAttrs = attrGroupService.getAttrGroupWithAttrsBySpuId(skuInfo.getSpuId(),skuInfo.getCatalogId());
                System.out.println(groupAttrs);
                skuItemVo.setGroupAttrs(groupAttrs);
        }, executor);
     
        //等待所有任务完成
        try {
                CompletableFuture.allOf(saleAttrFuture,descFuture,attrGroupFuture,imageFuture).get() ;
        } catch (InterruptedException e) {
                log.error("查询商品详情异步编排错误: ");
                log.error(e.getMessage() );
        } catch (ExecutionException e) {
                log.error(e.getMessage() );
        }
     
        return skuItemVo;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
  • 相关阅读:
    leetcode每天5题-Day20
    JavaScript -- 06.函数知识汇总
    嵌入式开发:技巧和窍门——提高嵌入式软件代码质量的7个技巧
    VMware 虚拟机安装 OpenWrt 作旁路由 单臂路由 img 镜像转 vmdk 旁路由无法上网 没网络
    JavaWeb-02:XML的学习
    cpp浅析STL-set
    10-2 Prometheus本地存储机制,单机远端存储
    JAVA:实现二个数字的通用根算法(附完整源码)
    WIN10访问Ubuntu的Samba
    【笔记】逻辑斯蒂回归
  • 原文地址:https://blog.csdn.net/wuyongde0922/article/details/127960301