一次运用CompletableFuture异步编程优化功能的记录

前言

有一天,客户反馈在系统查看当前用户的待办事项列表加载有明显卡顿,要等好几秒才加载出列表数据,印象中,这个功能已经上线有一段时间了,由于前期系统规划问题,没有维护全局的审核明细表,也没有引入什么流程引擎来支撑审批流,该功能的实现是通过分别到各目标模块统计数据汇总返回的。

代码审查

业务逻辑大概如下,在主方法里面分别调用其它目标模块的统计数据,汇总返回

ini 复制代码
private static StatisticsVO statisticsMaintenanceData() {
    StatisticsVO statisticsVO = null;
    StopWatch stopWatch = new StopWatch("维保统计耗时统计");
    try {
        stopWatch.start();
        Long waitingAuditWorksheetCancelNum = getWaitingAuditWorksheetCancelNum();
        Long waitingAuditElevatorDeleteNum = getWaitingAuditElevatorDeleteNum();
        Long waitingAuditOutStockNum = getWaitingAuditOutStockNum();
        Long waitingAuditInStockNum = getWaitingAuditInStockNum();

        statisticsVO = StatisticsVO.builder()
                .waitingAuditWorksheetCancelNum(waitingAuditWorksheetCancelNum)
                .waitingAuditElevatorDeleteNum(waitingAuditElevatorDeleteNum)
                .waitingAuditOutStockNum(waitingAuditOutStockNum)
                .waitingAuditInStockNum(waitingAuditInStockNum)
                .build();
        stopWatch.stop();
    } catch (Exception e) {
      log.info("统计维保数据发生异常{}", e);
    }
    System.out.println(stopWatch.prettyPrint());
    return statisticsVO;

}

通过TimeUnit#sleep的线程休眠方式来模拟统计逻辑的耗时情况,

csharp 复制代码
private static Long getWaitingAuditWorksheetCancelNum() throws InterruptedException {
    TimeUnit.SECONDS.sleep(5);
    return 15L;

}

private static Long getWaitingAuditElevatorDeleteNum() throws InterruptedException {
    TimeUnit.SECONDS.sleep(3);
    return 10L;
}

private static Long getWaitingAuditOutStockNum() throws InterruptedException {
    TimeUnit.SECONDS.sleep(2);
    return 5L;
}

private static Long getWaitingAuditInStockNum() throws InterruptedException {
    TimeUnit.SECONDS.sleep(2);
    return 5L;

}

可以看出在主方法获取各个目标模块的统计方式是串行的,意味着就是接口统计耗时总时长=各模块数据统计耗时之和。

看到这里,我的第一个想法是审查一遍各个目标模块的的统计代码,分别查看各个统计模块对应数据表的数据量情况,再检索各统计模块的查询SQL在数据库层面有没有用到索引,先从数据库层面优化对应的查询性能,审查后,发现各个目标模块的统计数据量都不大,都是万级别的数据量,同时在SQL查询都用到了对应的索引,主要耗时是因为用户数据权限的筛选,要先在程序层面,获取当前用户所拥有的数据权限再去目标模块筛选统计对应的数据,所以只能从获取目标模块的统计数据的方式改造入手了。

引入CompletableFuture异步编程

什么是 CompletableFuture

CompletableFuture 是由Java8引入的,这让我们编写清晰可读的异步代码变得更加容易,该类功能比Future 更加强大。

在Java中CompletableFuture用于异步编程,异步通常意味着非阻塞,运行任务单独的线程,与主线程隔离。并且通过回调可以在主线程中得到异步任务的执行状态,是否完成和异常等信息。

通过这种方式,主线程不会被阻塞,不需要一直等到子线程完成。主线程可以并行的执行其他任务。使用这种并行方式,可以极大的提高程序的性能。

为什么要引入 CompletableFuture

在前面的统计场景中,我们可以把每个模块的统计当成一个个子任务,如果运用以往的逻辑实现,我们需要等待所有的子任务串行统计执行完,才能获取到统计结果,接口耗时时长=所有子任务执行耗时之和,而使用CompletableFuture的方式,我们可以使用多线程异步执行子任务,加快任务执行速度,接口耗时时长由最长的子任务执行耗时决定。

代码改造

ini 复制代码
private static StatisticsVO statisticsMaintenanceData() {
        StatisticsVO statisticsVO = null;
        StopWatch stopWatch = new StopWatch("维保统计耗时统计");
        stopWatch.start();
        try {
            CompletableFuture<Long> worksheetCancelFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return getWaitingAuditWorksheetCancelNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            });
            CompletableFuture<Long> elevatorDeleteFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return getWaitingAuditElevatorDeleteNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            });
            CompletableFuture<Long> outStockFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return getWaitingAuditOutStockNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            });
            CompletableFuture<Long> inStockFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return getWaitingAuditInStockNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            });


            CompletableFuture<Void> completableFuture = CompletableFuture.allOf(worksheetCancelFuture, elevatorDeleteFuture, outStockFuture, inStockFuture);
            // 等待所有子任务执行完成
            completableFuture.join();

            statisticsVO = StatisticsVO.builder()
                    .waitingAuditWorksheetCancelNum(worksheetCancelFuture.join())
                    .waitingAuditElevatorDeleteNum(elevatorDeleteFuture.join())
                    .waitingAuditOutStockNum(outStockFuture.join())
                    .waitingAuditInStockNum(inStockFuture.join())
                    .build();
            stopWatch.stop();
        } catch (Exception e) {
          log.info("统计维保数据发生异常{}", e);
        }
        System.out.println(stopWatch.prettyPrint());
        return statisticsVO;

    }

通过执行结果,我们可以很直观地观察到接口耗时时长的变化,CompletableFuture的更多好玩特性,感兴趣的小伙伴可以自行去研究实践,推荐研读该篇技术文章,讲得比较层层递进,便于理解,Java中的CompletableFuture原理与用法

相关推荐
canonical_entropy6 分钟前
不同的工作需要不同人格的AI大模型?
人工智能·后端·ai编程
IT_陈寒16 分钟前
Vite 5.0 终极优化指南:7个配置技巧让你的构建速度提升200%
前端·人工智能·后端
小熊学Java21 分钟前
基于 Spring Boot+Vue 的高校竞赛管理平台
vue.js·spring boot·后端
卓码软件测评3 小时前
第三方软件测试机构【性能测试工具用LoadRunner还是JMeter?】
java·功能测试·测试工具·jmeter·性能优化
钢门狂鸭5 小时前
关于rust的crates.io
开发语言·后端·rust
Lionel_SSL6 小时前
《深入理解Java虚拟机》第三章读书笔记:垃圾回收机制与内存管理
java·开发语言·jvm
记得开心一点嘛6 小时前
手搓Springboot
java·spring boot·spring
老华带你飞7 小时前
租房平台|租房管理平台小程序系统|基于java的租房系统 设计与实现(源码+数据库+文档)
java·数据库·小程序·vue·论文·毕设·租房系统管理平台
独行soc7 小时前
2025年渗透测试面试题总结-66(题目+回答)
java·网络·python·安全·web安全·adb·渗透测试
脑子慢且灵7 小时前
[JavaWeb]模拟一个简易的Tomcat服务(Servlet注解)
java·后端·servlet·tomcat·intellij-idea·web