记一次接口优化历程 CountDownLatch

问题 :接口查询时间长,原因在于接口中含有多个组合for循环,且循环中还有SQL调用、rpc调用。

在索引已经最优的情况下,由于n*m次查询,导致接口耗时很慢。

参考文档:https://blog.csdn.net/liu_da_da/article/details/124983187

复制代码
排查过程:
1.检查所有代码结构,查看每一个sql是否已经添加索引。
2.根据阿里云arms,查询接口链路调用,确认sql、rpc等已经最简,且无法优化。
3.思路:使用多个线程,同时去查询,最后汇总结果返回;

优化1:

java 复制代码
List<ClassifyInfo> parentList = classifyInfoMapper.selectList(Wrappers.<ClassifyInfo>lambdaQuery()
        .select(ClassifyInfo::getId, ClassifyInfo::getParentId)
        .eq(ClassifyInfo::getSceneCode, param.getSceneCode())
);
List<ContractFileClassifyDTO> list = new ArrayList<>();
if (CollectionUtils.isNotEmpty(parentList)) {
    for (ClassifyInfo parent : parentList) {
        customTaskExecutor.execute(() -> {

		// 业务代码
            this.assembleFileClassify(list, parent);
        });
    }
}
return list;

异常:返回list为空?

原因:for循环时,一旦使用多线程,循环扔进线程池,本次循环立马结束,接着下一个循环。如此往复,最后直接返回list。

由于循环很快结束,而业务代码执行很慢,导致业务代码未执行完,list中未添加数据,接口已经结束,最后返回空。

解决:使用CountDownLatch,是一种同步工具,它可以使一个或多个线程等待其他线程完成操作后再继续执行。

也就是主线程要等待所有for循环子线程执行完毕之后,再统一汇总返回结果。

优化2:

java 复制代码
// 1.根据合同场景,查询合同大类
List<ClassifyInfo> parentList = classifyInfoMapper.selectList(Wrappers.<ClassifyInfo>lambdaQuery()
        .select(ClassifyInfo::getId, ClassifyInfo::getParentId)
        .eq(ClassifyInfo::getSceneCode, param.getSceneCode())
);
CountDownLatch latch = new CountDownLatch(parentList.size());
List<ContractFileClassifyDTO> list = new ArrayList<>();
if (CollectionUtils.isNotEmpty(parentList)) {
    for (ClassifyInfo parent : parentList) {
        customTaskExecutor.execute(() -> {
            this.assembleFileClassify(list, parent);
            // 当前线程执行完毕,计数器减1
            latch.countDown();
        });
    }
}
// 等待所有线程执行完毕,统一返回结果
latch.await();
return list;

优化3:

普通List 改为 CopyOnWriteArrayList ,线程安全

java 复制代码
// 1.根据合同场景,查询合同大类
List<ClassifyInfo> parentList = classifyInfoMapper.selectList(Wrappers.<ClassifyInfo>lambdaQuery()
        .select(ClassifyInfo::getId, ClassifyInfo::getParentId)
        .eq(ClassifyInfo::getSceneCode, param.getSceneCode())
);
if (CollectionUtils.isEmpty(parentList)) {
    return Collections.emptyList();
}

CountDownLatch latch = new CountDownLatch(parentList.size());
CopyOnWriteArrayList<ContractFileClassifyDTO> list = new CopyOnWriteArrayList<>();
for (ClassifyInfo parent : parentList) {
    customTaskExecutor.execute(() -> {
        this.assembleFileClassify(list, parent);
        // 当前线程执行完毕,计数器减1
        latch.countDown();
    });
}
// 等待所有子线程执行完毕,统一返回结果
latch.await();
return list;

总结

未优化前,一个线程执行两个for循环,需要执行nm次,时间复杂度为n m

优化后,使用多线程,时间复杂度为n。

优化4:

当前某个子线程执行异常时,计数器额未减1,导致最后结果为空。

解决:添加 finally,无论线程是否正常执行完毕,计数器都减1

java 复制代码
// 1.根据合同场景,查询合同大类
List<ClassifyInfo> parentList = classifyInfoMapper.selectList(Wrappers.<ClassifyInfo>lambdaQuery()
        .select(ClassifyInfo::getId, ClassifyInfo::getParentId)
        .eq(ClassifyInfo::getSceneCode, param.getSceneCode())
);
if (CollectionUtils.isEmpty(parentList)) {
    return Collections.emptyList();
}

CountDownLatch latch = new CountDownLatch(parentList.size());
CopyOnWriteArrayList<ContractFileClassifyDTO> list = new CopyOnWriteArrayList<>();
for (ClassifyInfo parent : parentList) {
    customTaskExecutor.execute(() -> {
        try {
            this.assembleFileClassify(list, parent);
        } catch (Exception e) {
            log.error("组装文件分类异常:{}", parent);
        } finally {
            // 无论当前线程是否正常执行完毕,计数器都减1
            latch.countDown();
        }
    });
}
// 所有子线程执行完毕,等待的主线程恢复执行
latch.await();
return list;

解决:

1.添加try flnally,无论子线程是正常执行完毕,计数器都减1。

最终异常的那个线程数据,未返回。

2.用 boolean await(long timeout, TimeUnit unit),设置过期时间

执行顺序:

1.主线程 CountDownLatch latch = new CountDownLatch(companyGroup.size());

2.主线程 latch.await();

3.子线程 this.Oa0501DraftContract(param, map, fileClassify, entry);

4.子线程 latch.countDown();

5.主线程 return;

相关推荐
Flittly3 分钟前
【SpringSecurity新手村系列】(7)基于资源权限码(Authority)的接口权限控制实战
java·spring boot·安全
ECT-OS-JiuHuaShan17 分钟前
哲学的本质,是递归因果
java·开发语言·人工智能·科技·算法·机器学习·数学建模
倾听一世,繁花盛开33 分钟前
Java语言程序设计——篇十三(1)
java·开发语言·ide·eclipse
大腕先生35 分钟前
通用分页超详细介绍(附带源代码解析&页面展示效果)
xml·java·linux·服务器·开发语言·前端·idea
A_aspectJ39 分钟前
如何抓住Java开发岗的市场红利?从需求端反推学习路径
java·开发语言·职场和发展
XS0301061 小时前
Java 基础(九) IO流
java·开发语言·php
_Evan_Yao1 小时前
缓存金字塔上的红色闪电:Redis 如何借力 CPU 的 L1/L2/L3 与 TLB 飞驰
java·数据库·redis·后端·缓存
他是龙5511 小时前
68:Java 原生反序列化 & SpringBoot 攻防
java·开发语言·spring boot
西岭千秋雪_1 小时前
终战诏书.
java
嘻嘻哈哈樱桃1 小时前
牛客经典101题题解集--二叉树
java·数据结构·python·算法·leetcode·职场和发展