热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CompletableFuture多线程并发异步编程

现在的计算机处理器性能越来越强大,多核处理器越来越普遍,核心数也越来越多,使用多线程可以更加充分利用硬件的资源,不论是什么原

点击上方“猿芯”,选择“设为星标

后台回复"1024",有份惊喜送给面试的你


首先,因为现在的应用越来越复杂,越来越多模块多系统之间相互依赖,一个操作可能需要使用多个模块或者多个系统提供的多个服务来完成一个功能,如果每个服务顺序的执行,可能需要消耗很多时间,或者前端用户需要得到及时响应,不需要等待所有服务完成便可以返回部分结果,而且现在的计算机处理器性能越来越强大,多核处理器越来越普遍,核心数也越来越多,使用多线程可以更加充分利用硬件的资源,不论是什么原因异步编程应运而生。


先看一下使用JDK5的Future接口简单的实现异步编程


代码

    //创建ExecutorService,并且通过它向线程池提交任务
    ExecutorService executorService = Executors. newCachedThreadPool();
    //向线程池提交任务
    Future future = executorService.submit( new Callable() {
    @Override
    public Double call() throws Exception {
    return doSomeLongTime();
    }
    });
    //当前线程继续做其他事情
    doSomeElse();
    //获取异步操作的结果
    try {
    double result = ( double)future.get( 1,TimeUnit. SECONDS);
    } catch (InterruptedException e) {
    //线程被中断
    e.printStackTrace();
    } catch (ExecutionException e) {
    //线程抛出异常
    e.printStackTrace();
    } catch (TimeoutException e) {
    //线程超时
    e.printStackTrace();
    }


    示意图


    既然要讲CompletableFuture那么明显Future接口肯定有很多局限性, 我们需要更多的特性:


    1. 将两个异步计算的结果组合成一个结果,并且这两个异步计算时相互独立的在不同到线程中,并且第二个计算依赖第一个计算。


    2. 等待所有异步任务的完成。


    3. 等待所有异步任务中任意一个完成并且获得计算结果。


    4.编程式的完成异步任务。(手动提供一个结果)


    5.异步任务完成响应事件。


    CompletableFuture实现异步编程:

      public class Shop {
      private String name;
      private Random random = new Random();




      public Shop(String name) {
      this. name = name;
      }




      //直接获取价格
      public double getPrice(String product){
      return calculatePrice(product);
      }
      //模拟延迟
      public static void delay(){
      try {
      Thread. sleep( 1000L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      //模拟获取价格的服务
      private double calculatePrice(String product){
      delay();
      return random.nextDouble() * product.charAt( 0) + product.charAt( 1);
      }




      //异步获取价格
      public Future getPriceAsync(String product){
      CompletableFuture future = new CompletableFuture<>();
      new Thread(() -> {
      double price = calculatePrice(product);
      future.complete(price);
      }).start();
      return future;
      }
      }


      调用异步接口:

        public class Client {
        public static void main(String[] args){
        Shop shop = new Shop( "BestShop");
        long start = System. currentTimeMillis();
        Future future = shop.getPriceAsync( "My Favorite");
        long invocatiOnTime= System. currentTimeMillis() - start;
        System. out.println( "调用接口时间:" + invocationTime + "毫秒");




        doSomethingElse();




        try {
        double price = future.get();
        } catch (InterruptedException e) {
        e.printStackTrace();
        } catch (ExecutionException e) {
        e.printStackTrace();
        }




        long retrievalTime = System. currentTimeMillis() - start;
        System. out.println( "返回价格消耗时间:" + retrievalTime + "毫秒");




        }




        public static void doSomethingElse(){
        System. out.println( "做其他的事情。。。");
        }
        }

        打印结果:

        调用接口时间:127毫秒

        做其他的事情。。。

        返回价格消耗时间:1127毫秒


        并行操作 Streams 和CompletableFutures 比较 

        1. 如果有大量计算的操作而没有I/O 操作(包括连接互联网),那么使用异步的  Streams 可以得到最好的性能。

        2. 相反如果有很多io操作, 使用 CompletableFutures可以得到更好的编弹性。


        parallelStream的并行操作:

          public static void main(String[] args){
          long start = System. currentTimeMillis();
          System. out.println( findPrice( "java8实战"));
          long duration = System. currentTimeMillis() - start;
          System. out.println( "总消耗时间:" + duration + "毫秒");
          }




          public static List findPrice(String product){
          List shops = Arrays. asList( new Shop( "sunjin.org"),
          new Shop( "加瓦匠"),
          new Shop( "京东商城"),
          new Shop( "天猫商城"));
          return shops.parallelStream()
          .map(shop -> String. format( "%s 的价格是 %.2f", shop.getName(),shop.getPrice(product)))
          .collect(Collectors. toList());




          }

          执行结果:

          [sunjin.org 的价格是 177.48, 加瓦匠 的价格是 189.90, 京东商城 的价格是 155.85, 天猫商城 的价格是 163.67]

          总消耗时间:1207毫秒


          CompletableFuture的并行操作

            public static List<String> findPrice2(String product){
            List<CompletableFuture<String>> priceFuture = shops.stream()
            .map(shop -> CompletableFuture. supplyAsync( // 使用异步的方式计算每种商品的价格
            () -> shop.getName() + " 的价格是 " + shop.getPrice(product)))
            .collect( toList());




            return priceFuture.stream()
            .map(CompletableFuture::join) //join 操作等待所有异步操作的结果
            .collect( toList());
            }
            long start = System. currentTimeMillis();
            System. out.println( findPrice2( "java8实战"));
            long duration = System. currentTimeMillis() - start;
            System.out.println("总消耗时间:" + duration + "毫秒");

            执行结果:

              [sunjin.org 的价格是 163.26300732704362, 加瓦匠 的价格是 172.3990021133357, 京东商城 的价格是 143.43284048828846, 天猫商城 的价格是 139.768245677111]
              总消耗时间:2235毫秒


              很明显这个操作比parallelStream慢了一倍


              多个任务的流水线操作


                获取价格字符串
                public String getPrice2(String product){
                double price = calculatePrice(product);
                Discount.Code code = Discount.Code. values()[ random.nextInt(Discount.Code. values(). length)];
                return String. format( "s%:%.2f:%s", name, price, code);
                }




                解析价格字符串的报价类
                public class Quote {
                private final String shopName;
                private final double price;
                private final Discount.Code code;




                public Quote(String shopName, double price, Discount.Code code) {
                this. shopName = shopName;
                this. price = price;
                this. code = code;
                }




                public String getShopName() {
                return shopName;
                }




                public double getPrice() {
                return price;
                }




                public Discount.Code getCode() {
                return code;
                }




                public static Quote parse(String s){
                String[] arr = s.split( ":");
                return new Quote(arr[ 0], Double. valueOf(arr[ 1]), Discount.Code. valueOf(arr[ 2]));
                }
                }


                折扣类型,包含枚举类型 和应用报价的方法。

                  public class Discount {
                  public enum Code{
                  NONE( 0), SILVER( 5), GOLD( 10), PLATINUM( 15), DIAMOND( 20);




                  private final int percentage;
                  Code( int percentage){
                  this. percentage = percentage;
                  }
                  }

                  public static String applyDiscount(Quote quote){
                  return quote.getShopName() + " price is " + Discount. apply(quote.getPrice(),quote.getCode());
                  }


                  public static double apply( double price, Code code){
                  delay();
                  return price * ( 100 - code. percentage) 100 ;
                  }




                  public static void delay(){
                  try {
                  Thread. sleep( 1000L);
                  } catch (InterruptedException e) {
                  e.printStackTrace();
                  }
                  }
                  }




                  public static List findPrice3(String product){
                  return shops.stream()
                  .map(shop -> shop.getPrice2(product)) //获取原始报价
                  .map(Quote:: parse) //解析报价字符串
                  .map(Discount:: applyDiscount) //调用折扣服务应用报价折扣
                  .collect( toList());
                  }

                  getPrice 请求耗时1秒, applyDiscount请求耗时1秒, 这是同步方法调用, 由于Stream中间操作的延迟性, 应用 五个商店, 这个方法整体将会耗时10秒。


                  使用CompletableFuture s异步操作

                    public static List<String> findPrice4(String product){
                    List<CompletableFuture<String>> priceFuture = shops.stream()
                    .map(shop -> CompletableFuture. supplyAsync( // 异步获取价格
                    () -> shop.getPrice2(product), executor))
                    .map(future -> future.thenApply(Quote:: parse)) // 获取到价格后对价格解析
                    .map(future -> future.thenCompose(quote -> CompletableFuture. supplyAsync( // 另一个异步任务构造异步应用报价
                    () -> Discount. applyDiscount(quote), executor)))
                    .collect( toList());




                    return priceFuture.stream()
                    .map(CompletableFuture::join) //join 操作和get操作有相同的含义,等待所有异步操作的结果。
                    .collect( toList());
                    }

                    这是用 CompletableFuture 的实现, 是异步的调用,但是 Stream中间操作的延迟性依然存在, 仍然应用五个商店, 

                    获取报价 解析报价 应用报价 这三个操作是顺序的,但是5个商店的任务是并行的,所以整个耗时2秒。


                    构造同步操作和异步任务示意图


                    如果两个异步操作是相互独立的

                      List> priceFuture = shops.stream()
                      .map(shop -> CompletableFuture. supplyAsync( // 异步获取价格
                      () -> shop.getPrice(product), executor))
                      .map(future -> future.thenCombine(CompletableFuture. supplyAsync( // 异步获取折扣率
                      () -> Discount. getRate(), executor)
                      , (price, rate) -> price * rate)) // 将两个异步任务的结果合并
                      .collect(toList());


                      将两个独立的 CompletableFuture s组合,因为操作是并行的,任务也是并行的,所以最终5个商店10个操作将在1秒左右执行结束。


                      ————————————————

                      版权声明:本文为CSDN博主「Neil Parker」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

                      原文链接:https://blog.csdn.net/sunjin9418/article/details/53321818


                       
                      往期推荐

                       

                      1. 肝九千字长文 | MyBatis-Plus 码之重器 lambda 表达式使用指南,开发效率瞬间提升80%

                      2. 用 MHA 做 MySQL 读写分离,频繁爆发线上生产事故后,泪奔分享 Druid 连接池参数优化实战

                      3. 微服务架构下,解决数据库跨库查询的一些思路

                      4. 一文读懂阿里大中台、小前台战略


                      作者简介猿芯,一枚简单的北漂程序员。喜欢用简单的文字记录工作与生活中的点点滴滴,愿与你一起分享程序员灵魂深处真正的内心独白。我的微信号:WooolaDunzung,公众号【猿芯输入 1024 ,有份面试惊喜送给你哦

                      【猿芯】
                       微信扫描二维码,关注我的公众号。

                      分享不易,莫要干想,如果觉得有点用的话,动动你的发财之手,一键三连击:分享、点赞、在看,你们的鼓励是我分享优质文章的最强动力 ^_^

                      分享、点赞、在看,3连3连!




                      推荐阅读
                      • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
                      • Java容器中的compareto方法排序原理解析
                        本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
                      • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
                      • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
                      • 模板引擎StringTemplate的使用方法和特点
                        本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
                      • Java太阳系小游戏分析和源码详解
                        本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
                      • Spring特性实现接口多类的动态调用详解
                        本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
                      • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
                      • [大整数乘法] java代码实现
                        本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
                      • Android源码深入理解JNI技术的概述和应用
                        本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
                      • Java学习笔记之面向对象编程(OOP)
                        本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
                      • Java SE从入门到放弃(三)的逻辑运算符详解
                        本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
                      • 图像因存在错误而无法显示 ... [详细]
                      • Android自定义控件绘图篇之Paint函数大汇总
                        本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
                      • 代理模式的详细介绍及应用场景
                        代理模式是一种在软件开发中常用的设计模式,通过在客户端和目标对象之间增加一层中间层,让代理对象代替目标对象进行访问,从而简化系统的复杂性。代理模式可以根据不同的使用目的分为远程代理、虚拟代理、Copy-on-Write代理、保护代理、防火墙代理、智能引用代理和Cache代理等几种。本文将详细介绍代理模式的原理和应用场景。 ... [详细]
                      author-avatar
                      小爬虫
                      这个家伙很懒,什么也没留下!
                      PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
                      Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有