CompletableFuture 异步加载的实现

         本文介绍了CompletableFuture并行开发的基本原理,并提供了一些实践案例,希望对大家理解并行开发提供一些帮助。

问题背景

  • APP首页作为前台核心导购流程,需要将 【附近门店】,【推荐商品】,【banner图】,【活动页】等多个中台能力聚合成更具象化的业务流程;
  • 但是各个中台的能力接口性能不尽相同(因为中台不关心业务):比如附近门店接口99线接近300ms,推荐商品99线接近200ms
  • 基于以上2点,业务研发就面临了一个核心问题:如果将多个性能参差不齐的中台接口聚合成用户体验好,性能高的业务流程

核心接口依赖分析

功能依赖接口平响(ms)99线(ms)备注
附近门店shop/nearbyStore66101根据经纬度获取附近门店
推荐商品product/getRecommendCommodities170260获取销量前五的商品
活动信息activity/activityInformation62121获取配置的活动
banner图operation/getBanner2443获取APP Banner

如大家所见,如果要渲染出所有的信息,则平响至少需要322ms;  99线至少需要:525ms,作为APP核心入口之一,这样的性能显然是不过关的。

如何解决性能问题

寻找解决方案

面对该问题时尝试做了以下推演:

  1. 敦促下游依赖提升服务性能:
  2. 将页面所需数据进行分批下发,进行异步渲染:
推演方向可行性分析最终结论
敦促下游依赖提升服务性能– 可行性低
– 投入产出比低
无效推演
将页面所需数据进行分批下发,进行异步渲染核心数据均在首屏加载完毕无效推演
异步加载数据业务流程中存在较多无直接依赖数据可尝试并行加载有效推演

并行加载技术方案的选取 

技术方向优点缺点最终选择
Future– JDK自带
– 简单易上手
只能通过阻塞或者轮询方式获取返回值淘汰
CompletableFuture– JDK自带
– 简单易上手
– 异步任务结束或者出错时,会自动回调某个方法
– 主线程设置回调后,无需关心异步任务的执行
入选
RxJava– 学习成本高
– 公司内部暂未推广使用
淘汰
Reactor– 学习成本高
– 公司内部暂未推广使用
淘汰

CompletableFuture 异步加载的实现 

CompletableFuture通过异步回调的方式,解决了开发过程中异步调用获取结果的难点。开发人员只需接触到CompletableFuture对象,以及CompletableFuture任务的执行结果,无需设计具体异步回调的实现,并可通过自定义线程池进一步优化任务的异步调用。

 CompletableFuture 的实例化

 CompletableFuture提供了四个静态方法来创建一个实例

构造方法说明

方法声明说明
public static CompletableFuture<Void> runAsync(Runnable runnable)– 无需返回结果的异步调用
– 使用默认的线程池 ForkJoinPool.commonPool() 
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)– 无需返回结果的异步调用
– 使用用户自定义的线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)– 需要返回值的异步调用
– 使用默认的线程池 ForkJoinPool.commonPool() 
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)– 需要返回值的异步调用
– 使用用户自定义的线程池

 代码示例

  • 无返回值且未指定线程池的 
public static void main(String[] args) {
        CompletableFuture<Void> result = CompletableFuture.runAsync(
                () -> testNoResult("JF") );
        int age = 11;
        System.out.println("JF今年的年纪是" + age);
    }
 
    public static void  testNoResult(String name) {
        System.out.println(name +" 调用了 没有返回值的异步方法");
    }
  • 无返回值且指定了线程池
public static void main(String[] args) {
     TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
             TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
 
     CompletableFuture<Void> result = CompletableFuture.runAsync(
                     () -> testNoResult("JF"), threads);
      
 }
 
 public static void  testNoResult(String name) {
     System.out.println(name +" 调用了 没有返回值,但是制定了线程池的的异步方法");
 }
  • 有返回值且未指定线程池的
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
 
     CompletableFuture<String> result = CompletableFuture.supplyAsync(
                     () -> testNoResult("JF"));
 
     String resultDesc = result.get(1000, TimeUnit.MILLISECONDS);
     System.out.println(resultDesc);
 }
 
  public static String  testNoResult(String name) {
     return (name +" 调用了 没有返回值,但是制定了线程池的的异步方法");
 }

  • 有返回值且指定线程池
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
     TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
             TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
 
     CompletableFuture<String> result = CompletableFuture.supplyAsync(
                     () -> testNoResult("JF"), threads);
 
     String resultDesc = result.get(1000, TimeUnit.MILLISECONDS);
     System.out.println(resultDesc);
 }
 
 public static String  testNoResult(String name) {
     return (name +" 调用了 没有返回值,但是制定了线程池的的异步方法");
 }

获取任务执行执行结果 

方法声明说明
public T get();– 实现了Future接口的功能
– 会一直阻塞直到获取线程结果
public T get(long timeout, TimeUnit unit);– 实现了Future接口的功能
– 指定时间内仍未获取到结果会抛出异常
public T getNow(T valueIfAbsent);– 不会产生阻塞
– 如果任务未执行完成返回默认缺省值
public T join();一直阻塞直到获取线程结果
– 唯一不同就是 join 方法不会抛出检查时异常 

 触发任务完成

方法声明说明
public boolean complete(T value);– 主动触发当前异步任务的完成,如果已完成,该方法返回false
– 如果未完成则返回true,且其他线程将传入的 T value 当做返回值
public boolean completeExceptionally(Throwable ex);触发任务执行的异常

complete

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
 
        CompletableFuture<String> result = CompletableFuture.supplyAsync(
                        () -> testNoResult("JF"), threads);
 
        boolean complete = result.complete("Ascend 来插队了");
        System.out.println("并行是否完成?" + !complete +"   结果是  " + result.get());
    }
 
    public static String  testNoResult(String name)  {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            return "nothing";
 
        }
        return (name +" 调用了");
    }
 
 
===================================================================================
输出结果
 
并行是否完成?false   结果是  Ascend 来插队了

completeExceptionally

  public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        TaskThreadPoolExecutor threads = new TaskThreadPoolExecutor(20, 100, 30,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(500));
 
        CompletableFuture<String> result = CompletableFuture.supplyAsync(
                        () -> testNoResult("JF"), threads);
 
        BizException bizException = new BizException(BizErrorCodeEnum.PARAM_ERROR);
        boolean complete = result.completeExceptionally(bizException);
        System.out.println("并行是否完成?" + !complete +"   结果是  " + result.get());
    }
 
    public static String  testNoResult(String name)  {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            return "nothing";
 
        }
        return (name +" 调用了");
    }
 
===================================================================================
输出结果
 
Exception in thread "main" java.util.concurrent.ExecutionException: com.tuhu.boot.common.exceptions.BizException: 参数错误
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)
    at com.tuhu.cl.install.agg.service.common.TaskConfig.main(TaskConfig.java:49)
Caused by: com.tuhu.boot.common.exceptions.BizException: 参数错误
    at com.tuhu.cl.install.agg.service.common.TaskConfig.main(TaskConfig.java:47)

对任务执行结果进行下一步处理

只能接受任务正常执行后的回调

方法声明说明代码示例执行结果
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);拿到上一步任务执行的结果进行处理并且返回处理的结果
    CompletableFuture<String> completableFuture = 
    CompletableFuture.supplyAsync(() -> 10)
    .thenApply(v -> ("上一步的执行的结果为:" + v));
    System.out.println(completableFuture.join());

上一步的执行的结果为:10
public CompletableFuture<Void> thenRun(Runnable action);拿不到上一步任务执行的结果,但会执行Runnable接口的实现
   CompletableFuture<Void> completableFuture = 
       CompletableFuture.supplyAsync(() -> 10)
                        .thenRun(() 
                                -> System.out.println("上一步执行完成"));

上一步执行完成
public CompletionStage<Void> thenAccept(Consumer<? super T> action);可以拿到上一步任务执行的结果进行处理,但不需要返回处理的结果
CompletableFuture<Void> completableFuture =
CompletableFuture.supplyAsync(() -> 10).thenAccept(v -> 
System.out.println("上一步执行完成,结果为:" + v));

上一步执行完成,结果为:10

只能接收任务处理异常后的回调

方法声明说明代码示例执行结果

public CompletionStage<T> 

exceptionally(

Function<Throwable, ? extends T> fn);

– 任务执行过程中出现异常的时候,会回调exceptionally方法
– exceptionally能够将异常给吞了,并且fn的返回值会返回回去(有点降级的味道了)
CompletableFuture<Integer> completableFuture = 
CompletableFuture.supplyAsync(() -> {return 100;}).
exceptionally(e -> {System.out.println("出现异常了,
返回默认值");return 110;});
System.out.println(completableFuture.join());

出现异常了,返回默认值
100

能同时接收任务执行正常和异常的回调

方法声明说明代码示例执行结果

public <U> CompletionStage<U> 

handle(BiFunction<? super T,

Throwable, ? extends U> fn); 

– 跟exceptionally有点像,但是exceptionally是出现异常才会回调,handle正常情况下也能回调
– 两者都有默认返回值

public CompletionStage<T> 

whenComplete(BiConsumer<? 

super T, ? super Throwable> actin);

– 能接受正常或者异常的回调,并且不影响上个阶段的返回值,也就是主线程能获取到上个阶段的返回值
– 当出现异常时,无降级操作,主线程在获取执行异常任务的结果时,会抛出异常。
CompletableFuture<Integer> completableFuture 
= CompletableFuture.
     supplyAsync(() -> {
          int i = 1 / 0;
          return 10;
   }).whenComplete((r, e) -> {
     System.out.println("whenComplete被调用了");
   });
    System.out.println(completableFuture.join());

whenComplete
被调用了
Exception in thread 
“main” java.util.
concurrent.
Completion

Exception:

结果合并 

方法声明说明
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn);组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action)组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)组合两个 future,不需要获取 future 的结果,只需两个 future 处理完任务后,处理该任务

应用实践 

代码示例

@Resource("appHomeExecutor")
    private Executor executor;

    @Override
    public void initApp() {
        // 1:异步查询附近门店
        CompletableFuture<ShopInfoVo> shopCompletableFuture = CompletableFuture.supplyAsync(() -> {
            return new ShopInfoVo();
        }, executor);
        // 2:异步查询推荐商品
        CompletableFuture<ProductVo> productCompletableFuture = CompletableFuture.supplyAsync(() -> {
            return new ProductVo();
        }, executor);
        // 3:异步查询活动信息
        CompletableFuture<ActivityVo> activityVoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            return new ActivityVo();
        }, executor);
        // 4:异步查询banner图
        CompletableFuture<BannerVo> bannerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            return new BannerVo();
        }, executor);

        // 等待所有异步运算完成
        CompletableFuture.allOf(shopCompletableFuture, productCompletableFuture, activityVoCompletableFuture,
            bannerCompletableFuture);

        try {
            // 获取异步运算结果
            ShopInfoVo shopInfoVo = shopCompletableFuture.get();
            ProductVo productVo = productCompletableFuture.get();
            ActivityVo activityVo = activityVoCompletableFuture.get();
            BannerVo bannerVo = bannerCompletableFuture.get();

            // 组装响应数据

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

实际效果

性能指标平响95线99线
响应时间132162174

参考 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值