个人愚见:分布式框架中使用CompletableFuture提高效率

Future 接口的局限性

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。

  • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
  • 等待 Future 集合中的所有任务都完成。
  • 仅等待 Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
  • 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
  • 应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)

例如:问题1:目前有十个Future结果,想要达到最快遍历这十个结果集,但又不知道每个Future的运行时间,如果获取的第一个Future不是运行最快的哪个,那么就不是最优结果。相应的获取第二个Future如果不是第二快的结果,最后得到的结果也不是最优的。问题2:更坏的结果,就是其中一个Future出现异常,得不到结果,代码运行到这的时候就会卡在这。

模拟场景,查找一个商品,有在某宝上找的服务,有在某东上找,某猫上找、、、8个网店;

public class Shop {

    Random random = new Random();

    private String name;

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

    public static void delay() {
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public double getPice(String product) {
        delay();
        return random.nextDouble() * 100;
    }

    public Future<Double> getPriceAsync(String product) {
        //CompletableFuture<Double> futurePrice = new CompletableFuture<>();
        //new Thread(() -> futurePrice.complete(getPice(product))).start();
        /*
        * supplyAsync()该方法对线程异常进行处理,避免出现异常后的堵塞
        * */
        return CompletableFuture.supplyAsync(() -> (getPice(product)));
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) throws Exception {
        Shop show = new Shop("bestShow");
        long start = System.currentTimeMillis();
        Future<Double> futurePrice = show.getPriceAsync("some product");
        System.out.println("调用返回,耗时" + (System.currentTimeMillis() - start));
        double price = futurePrice.get();
        System.out.println("价格返回,耗时" + (System.currentTimeMillis() - start));

    }

}
public class PriceDemo {
    private List<Shop> shops = Arrays.asList(new Shop("shop1"),
            new Shop("shop2"),
            new Shop("shop3"),
            new Shop("shop4"),
            new Shop("shop5"),
            new Shop("shop6"),
            new Shop("shop7"),
            new Shop("shop8")
    );

    public List<String> findPrices(String product){
        /*
        * 方法一:加并行流.parallel()
        * */
        return shops.stream().map(shop -> String.format("%s price is %.2f ",shop.getName(),shop.getPice(product)))
                .collect(Collectors.toList());
    }
    public static void main (String[] args){
        PriceDemo priceDemo = new PriceDemo();
        Long start = System.currentTimeMillis();
        System.out.println(priceDemo.findPrices("苹果x"));
        System.out.println("服务耗时:"+(System.currentTimeMillis()-start));
    }
}

原始输出结果:一条数据一秒

[shop1 price is 71.48 , shop2 price is 76.57 , shop3 price is 58.08 , shop4 price is 21.46 , shop5 price is 96.94 , shop6 price is 56.09 , shop7 price is 8.10 , shop8 price is 92.37 ]
服务耗时:8266

 并行流输出结果(4核电脑):4核跑4条线程,所以是2秒

[shop1 price is 5.77 , shop2 price is 6.76 , shop3 price is 93.42 , shop4 price is 77.97 , shop5 price is 6.51 , shop6 price is 34.42 , shop7 price is 55.77 , shop8 price is 26.70 ]
服务耗时:2226

 使用CompletableFuture试试

 public List<String> findPrices(String product){
         List<CompletableFuture<String>> priceFuture = shops.stream().map(shop -> CompletableFuture
               .supplyAsync(() -> String.format("%s price is %.2f ", shop.getName(), shop.getPice(product))))
                 .collect(Collectors.toList());
       return priceFuture.stream().map(CompletableFuture::join).collect(Collectors.toList());
   }

 结果是3秒,有点失望,因为写了一大串东西,还不如并行流好用

[shop1 price is 20.29 , shop2 price is 72.29 , shop3 price is 80.94 , shop4 price is 97.70 , shop5 price is 46.41 , shop6 price is 40.96 , shop7 price is 16.88 , shop8 price is 11.10 ]
服务耗时:3303

       当调用查询服务加到9个的时候,并行流和CompletableFuture都是三秒,因为CompletableFuture默认的启动机制是和并行流一样的,但是CompletableFuture这个方法可以通过配置,创建一个线程池。

public List<String> findPrices(String product){
       Executor executor = Executors.newCachedThreadPool();
       //Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(),100));
       List<CompletableFuture<String>> priceFuture = shops.stream().map(shop -> CompletableFuture
               .supplyAsync(() -> String.format("%s price is %.2f ", shop.getName(), shop.getPice(product)),executor))
                 .collect(Collectors.toList());
       return priceFuture.stream().map(CompletableFuture::join).collect(Collectors.toList());
   }

[shop1 price is 45.65 , shop2 price is 10.77 , shop3 price is 73.20 , shop4 price is 76.88 , shop5 price is 64.87 , shop6 price is 97.15 , shop7 price is 75.77 , shop8 price is 54.31 , shop9 price is 55.49 , shop1 price is 16.00 , shop2 price is 71.55 , shop3 price is 59.17 , shop4 price is 65.65 , shop5 price is 58.00 , shop6 price is 38.72 , shop7 price is 18.05 , shop8 price is 45.38 ]
服务耗时:1249

这里查询任务给它开到了17个,1秒就搞定了!并行流要5秒 !

在这里,可以学习下别人的 newFixedThreadPool 配置线程数,当然,不精确的话 用这个newCachedThreadPool就完事了

《个人愚见:分布式框架中使用CompletableFuture提高效率》

N是cpu核数,U是CPU占用比

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

模拟场景2

       一般价格都会进行打折促销,所以我们要在获取价格后获取打折的信息,这些东西还都是从不同国家地方来的,所以还需要查询汇率。

       小小分析一波:因为汇率是和价格、折扣是没有关系的,所以汇率可以和价格或者和折扣一块进行查询,折扣是在价格的基础上进行打折,所以在价格服务调用玩之后才能进行折扣服务。

下面看下具体代码

//折扣 定义查询折扣的时候,是1秒
public enum Discount {

    NONE(0),SILVER(5),GOLD(10),PLATNUM(15),DIAMOND(20);

    private final int percent;

    Discount(int percent){
        this.percent = percent;
    }

    public int getPercent() {
        return percent;
    }

}
public class DiscountDemo {

    public static void delay() {
       
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static String applyDiscount(Quote quote){
        return quote.getShop()+" price is " +apply(quote.getPrice(),quote.getDiscount());
    }

    private static String apply(double price,Discount discussion) {
        delay();
        return NumberFormat.getInstance().format(price * (100 - discussion.getPercent())/100);
    }

}
//汇率的服务 定义也是1秒
public class ExchangeDemo {

    public static void delay() {
        try {
            Thread.sleep(1000l);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
}
    public static double getRate(String source,String target){
        delay();
        return 10;
    }

}

 调用方法:

public List<String> findPrices(String product) {
     Executor executor = Executors.newCachedThreadPool();
    // Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(),100));
     List<CompletableFuture<String>> priceFuture = shops.stream()
             .map(shop -> CompletableFuture
                     .supplyAsync(() -> shop.getPice(product), executor)
                     .thenCombine(CompletableFuture.supplyAsync(() -> ExchangeDemo.getRate("USD", "CNY"), executor),
                             (quote, rate) -> new Quote(quote.getShop(), quote.getPrice() * rate, quote.getDiscount())))//这返回的是异步处理
                     //.map(future->future.thenApply(Quote::parse))//thenApp是前一个对象完成了之后调下个对象的方法(parse)
             .map(future -> future.thenCompose(quote ->//thenCompose是当前对象准备扔到一个异步操作里面
                     CompletableFuture.supplyAsync(() ->   DiscountDemo.applyDiscount(quote), executor)))
             .collect(Collectors.toList());
     return priceFuture.stream().map(CompletableFuture::join).collect(Collectors.toList());
 }

 [shop1 price is 1,000, shop2 price is 850, shop3 price is 850, shop4 price is 950, shop5 price is 1,000, shop6 price is 850, shop7 price is 900, shop8 price is 900, shop9 price is 1,000, shop10 price is 900, shop11 price is 900, shop12 price is 900, shop13 price is 950, shop14 price is 950, shop15 price is 850, shop16 price is 950, shop17 price is 850]
服务耗时:2140

           输出结果:2秒,原因是汇率和价格并发,再调用打折服务。

模拟场景3:2秒(实际上可能更多),对于客户体验来说,应该算是差的,我接受不了淘宝2秒才给我查出我想要的。

         那个查询快就先显示哪个

先改造一下查询时间,在获取价格和汇率、折扣方法都改成

public static void delay() {
        Random random = new Random();
       int delay = 500 + random.nextInt(2000);
        try {
            Thread.sleep(delay);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

调用方法改成:

public  void findPrices(String product){

        long start = System.currentTimeMillis();

       Executor executor = Executors.newCachedThreadPool();
       //Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(),100));
       CompletableFuture<?>[] priceFuture = shops.stream()
               .map(shop -> CompletableFuture
                       .supplyAsync(() -> shop.getPice(product), executor)
                       .thenCombine(CompletableFuture.supplyAsync(()->ExchangeDemo.getRate("USD","CNY"),executor),
                               (quote, rate) -> new Quote(quote.getShop(),quote.getPrice()*rate,quote.getDiscount())))//这返回的是异步处理
                       //.map(future->future.thenApply(Quote::parse))//thenApp是前一个对象完成了之后调下个对象的方法(parse)
               .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> DiscountDemo.applyDiscount(quote), executor)))
               //thenAccept()定义CompletableFuture返回的结果
               .map(future -> future.thenAccept(content -> System.out.println(content + "(done in " + (System.currentTimeMillis() - start) + " msecs")))
               .toArray(size->new  CompletableFuture[size]);
        //allOf接收一个数组,当里面的CompletableFuture都完成的时候,就会执行下一个语句
        CompletableFuture.allOf(priceFuture).thenAccept((obj)->System.out.println(" all done"));
        //allOf接收一个数组,当里面的CompletableFuture有一个完成时,就会执行下一个语句
        CompletableFuture.anyOf(priceFuture).thenAccept((obj) -> System.out.println("fastest anyOf done " + obj));

   }

结果:

服务耗时:133
shop2 price is 900(done in 1572 msecs
fastest anyOf done null
shop16 price is 900(done in 2447 msecs
shop12 price is 1,000(done in 2637 msecs
shop13 price is 900(done in 2714 msecs
shop14 price is 800(done in 2908 msecs
shop10 price is 850(done in 3185 msecs
shop3 price is 1,000(done in 3215 msecs
shop5 price is 850(done in 3448 msecs
shop4 price is 850(done in 3662 msecs
shop7 price is 1,000(done in 3844 msecs
shop8 price is 850(done in 4110 msecs
shop11 price is 900(done in 4244 msecs
shop6 price is 950(done in 4316 msecs
shop1 price is 950(done in 4476 msecs
shop9 price is 850(done in 4614 msecs
shop15 price is 850(done in 4730 msecs
shop17 price is 850(done in 4797 msecs
 all done

 这样就达到了我们想要的目的,哪个价格先找出来就显示哪个,不用等程序一块执行完才一块显示.

车轮造完了,看着视频敲的,记录一下。

    原文作者:QQ-hoop
    原文地址: https://blog.csdn.net/qq_36597450/article/details/81232051
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞