CompletableFuture-简单使用

  • 前言

    开发中遇到一个简单问题,一个接口对第三方api有依赖,而且是依赖三个,导致这个接口的响应速度完全依赖于第三方接口的响应速度,前期开发并未考虑性能问题,将三个依赖的API请求做成串行,导致的结果是接口总耗时是三个api接口耗时的总和,接口响应变得巨慢无比,前端经常说接口动不动就超时,通过性能分析所有的耗时基本都是第三方响应过慢。

    第三方接口的响应时间我们无法处理,但是我们可以改变我们的调用模式使接口总耗时变短,之前使用串行调用,接口的耗时是三个API总耗时,我们将调用方式变为并行,然后等三个API都有返回时进行下一步逻辑处理,这样总耗时就变为三个API中最长耗时调用,这样接口响应将变快很多。

  • Future

    怎样进行并行任务?查阅资料发现提到了Future,Future 是java1.5 出现的,算是一个古老的API,翻开源码就可以看到其介绍:

    基本的意思是,能用来异步计算,可以拿到结果,可以取消任务。基本符合我们的使用场景。

    使用方式:

    public void testFuture() {
        //1.建立执行器(需要根据实际业务场景创建)
        ExecutorService executor = Executors.newSingleThreadExecutor();
        //2.提交需要执行的任务
        Future<String> submitFirst = executor.submit(this::taskFirst);
        Future<List<String>> submitSecond = executor.submit(this::taskSecond);
        Future<Map<String, String>> submitThird = executor.submit(this::taskThird);
        //3.轮询获取执行任务完成状态
        while (!submitFirst.isDone()
                || !submitSecond.isDone()
                || !submitThird.isDone()) {
            //todo 超时强行取消任务
        }
        //4. 拿到结果进行后处理
        try {
            String resultFirst = submitFirst.get();
            List<String> resultSecond = submitSecond.get();
            Map<String, String> resultThird = submitThird.get();
            // TODO: 处理结果
    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    注意到我们拿到执行结果的方式,轮询!!轮询的方式耗费无谓的CPU资源,而且也不能及时地得到计算结果,是否能通过
    别的方式获取到结果?还可以通过阻塞的方式获取到结果,我们既然使用异步处理任务又需要阻塞主线程获取结果,与初衷
    完全背道而驰了。

    如果我们的子任务出现异常,在Future中没有提供异常处理的API的,处理异常也是个很麻烦的问题。

    所以说Future是有缺陷的。

  • CompletableFuture

    怎样弥补Future缺陷?Java8给出了答案,使用CompletableFuture。

    简单查阅下源码说明:

    我们注意到有提到 CompletionStage,简单看下介绍:

    大致意思是:定义了一组接口用于在一个阶段执行结束之后,要么继续执行下一个阶段,要么对结果进行转换产生新的结果。个人
    理解有点像Java8的Stream。

    CompletableFuture 实现了Future 与 CompletionStage 为了就是弥补 Future的各种不足。

    使用方式:

      public void testCompletableFuture() {
          //1.建立执行器(需要根据实际业务场景创建)
          ExecutorService executor = Executors.newSingleThreadExecutor();
          //2.添加任务
          CompletableFuture<String> firstCompletableFuture = CompletableFuture
                  .supplyAsync(this::taskFirst, executor)
                  .exceptionally(throwable -> null);
          CompletableFuture<List<String>> secondCompletableFuture = CompletableFuture
                  .supplyAsync(this::taskSecond, executor);
          CompletableFuture<Map<String, String>> thirdCompletableFuture = CompletableFuture.
                  supplyAsync(this::taskThird, executor);
          //依赖与任务一的完成
          CompletableFuture<String> lastCompletableFuture = firstCompletableFuture
                  .whenCompleteAsync((s, throwable) -> taskThird())
                  .exceptionally(throwable -> null);
          //3. 合并所有任务
          CompletableFuture<Void> totalFuture = CompletableFuture.allOf(firstCompletableFuture,
                  secondCompletableFuture,
                  thirdCompletableFuture,
                  lastCompletableFuture);
          try {
              //4.所有任务完成,可以选择指定总超时时间
              totalFuture.get(4, TimeUnit.SECONDS);
    
              //5.获取每一个任务的结果
              String firstResult = firstCompletableFuture.get(1, TimeUnit.SECONDS);
              List<String> secondResult = secondCompletableFuture.get(1, TimeUnit.SECONDS);
              Map<String, String> thirdResult = thirdCompletableFuture.get(1, TimeUnit.SECONDS);
              String lastResult = lastCompletableFuture.get(1, TimeUnit.SECONDS);
              //todo 处理结果
    
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    

    用起来比Future更加简单,获取结果的方式也更加又优雅,并且支持异常的处理。查看CompletableFuture的使用教程发现
    CompletableFuture支持各种任务组合使用,提供各种强大的API,使用起来更加灵活。

  • 想法

    抛开CompletableFuture使用,回到文章开始的场景,其实可以理解为三个任务并行,它们之间没有多大关系,但是它们的结果又决定最终的大结果,换个角度理解为:我们把一个大的任务分成了三个小任务,三个小任务各自负责自己的模块,各个任务的结果合并成为大任务的结果,这种场景可以认为是分治思想的使用,其实细想,代码中很多地方都可以认为用到分治的思想,我们总是把很多方法的结果合并起来达到最终结果,这样也是分治。工作中生活中我们也会把一些大的任务拆分成很多细小的任务,单个执行,最终合并结果,这样任务会有条不紊的完成而不会变得错综复杂。

  • 参考文档

    掘金 CompletableFuture 教程

    掘金 Java 8新特性之CompletableFuture

    Java CompletableFuture 详解

    20个使用 Java CompletableFuture的例子

posted @ 2023-02-09 14:05  年年糕  阅读(30)  评论(0编辑  收藏  举报