🚀分享一下最近实习的产出-CompletableFuture优化库位禁用检查性能
如题就是单纯的技术分享,看了不吃亏
业务背景
最近接到的一个任务就是一个查询性能太低了(已经被我拿下了),需要我去拿方案,(因为有我导师兜底),有五个服务都需要查询loc,也就是库位禁用检查,在WMS系统中库位是存储商品的基本单位,当需要禁用某个库位时,必须确保该loc没有被正在进行的业务单据占用。
人话:也就是说可以查询这个库位上是否还存在东西,是否有未完成的任务,没有就可以禁用库位,也可以按照库位维度去查询这个任务完成情况
一开始的代码是串联查询完成后,在返回,但是性能太低了,我一开始是去改底层的sql看看能不能解决这个问题,框架层也已经实现了分库分表,直接用就行,但是sql的调优也解决不了这个性能问题,测试那边加压,还是会有性能问题,后面就在想如何解决,导师叫我用线程池,正好有这个机会,我对CompletableFuture不熟悉就学习+使用了
具体实现
代码已经脱敏
▼java复制代码// 数据流转过程 输入: List<LocationDisableLocDTO> locDTOList ↓ 提取: List<Long> locIds (库位ID列表) ↓ 并行查询: 5个 CompletableFuture 同时执行 这里是核心 ↓ 结果: 5个 Map<Long, String> (库位ID -> 单据号) ↓ 合并处理: 遍历每个库位,检查是否有未完成的单据 ↓ 输出: Map<Long, LocationDisableLocResp> locRespMa
有五个服务都需要去查询这个库位 使用这里
▼java复制代码public void checkLocations(List<LocationDTO> locations, Map<Long, LocationCheckResult> resultMap) { // 1. 参数校验 if (locations == null || locations.isEmpty()) { return; // 如果库位列表为空,直接返回 } // 2. 获取当前上下文信息(示例:仓库ID和公司ID) Long warehouseId = ContextUtil.getCurrentWarehouseId(); Long companyId = ContextUtil.getCurrentCompanyId(); // 3. 准备查询参数 List<Long> locationIds = locations.stream() .map(LocationDTO::getId) // 提取库位ID .collect(Collectors.toList()); LocationQueryRequest queryRequest = new LocationQueryRequest(); queryRequest.setLocationIds(locationIds); // 设置库位ID列表 queryRequest.setWarehouseId(warehouseId); // 设置仓库ID //以第一个CompletableFuture为代表 // 异步查询未完成任务示例 CompletableFuture<Map<Long, String>> CompletableFuture1 = CompletableFuture.supplyAsync(() -> { try { // 设置线程上下文(例如仓库ID、公司ID) ContextUtil.setWarehouseId(warehouseId); ContextUtil.setCompanyId(companyId); // 执行查询并转换结果格式 return taskService.queryUnfinishedTasks(queryRequest).stream() .collect(Collectors.toMap( TaskDTO::getLocationId, // 键:库位ID TaskDTO::getTaskNumber // 值:任务编号 )); } catch (Exception e) { throw e; // 异常直接抛出,教学示例中可简单处理 } finally { // 清理线程上下文 ContextUtil.setWarehouseId(null); ContextUtil.setCompanyId(null); } }, ThreadPoolUtils.getExecutorService()); // 使用自定义线程池 CompletableFuture2.supplyAsync CompletableFuture3.supplyAsync CompletableFuture4.supplyAsync CompletableFuture5.supplyAsync
详细解释:
- CompletableFuture.supplyAsync():
- 创建一个异步任务
- 返回一个 CompletableFuture<Map<Long, String>> 对象
- 这个对象代表"将来会返回一个 Map<Long, String> 类型的结果"
- Lambda表达式 () -> { ... }:
- 这是要异步执行的任务
- 相当于一个独立的线程在执行这段代码
等待所有任务完成
▼java复制代码CompletableFuture.allOf(CompletableFuture1, CompletableFuture2, CompletableFuture3, CompletableFuture4, CompletableFuture5); // allOf 的作用: // 1. 等待所有传入的 CompletableFuture 都完成 // 2. 如果任何一个任务失败,整个 allOf 也会失败 // 3. 这是一个阻塞操作,会等待所有任务完成 // 示例: CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> { Thread.sleep(1000); // 模拟耗时1秒 return "任务1完成"; }); CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> { Thread.sleep(2000); // 模拟耗时2秒 return "任务2完成"; }); // 等待两个任务都完成 CompletableFuture.allOf(task1, task2); // 这里会阻塞,直到两个任务都完成(2秒后)
获取所有结果
▼java复制代码// 获取未完成CompletableFuture1的结果 Map<Long, String> notFinishReplMap = CompletableFuture1.join(); ....... //剩下四个接口一样 // join() 方法: // 1. 获取 CompletableFuture 的结果 // 2. 如果任务还没完成,会阻塞等待 // 3. 如果任务失败,会抛出异常 // 示例: CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "异步任务结果"; }); String result = future.join(); // 获取结果 System.out.println(result); // 输出:异步任务结果
业务逻辑处理
▼java复制代码......都是java业务逻辑代码 结束
为什么要用
1.库位禁用检查需要同时查询多种不同类型的未完成单据 2.每种单据的查询都是独立的数据库操作,可以并行执行 3.使用CompletableFuture可以实现真正的并发执行,而不是串行执行
使用场景
1.库位禁用是一个批量操作,需要检查大量库位的状态 2.每个库位需要检查多种业务单据的关联关系 3.最最重要的是这些检查操作之间没有依赖关系,可以并行处理
为什么不直接使用线程池
1.需要线程上下文管理每个异步任务都有独立的线程上下文设置和清理 2.虽然能并发,但代码可读性差,异常处理麻烦。 总的来说,使用CompletableFuture而不是直接开线程池是一个很好的设计选择,既保证了性能,又提供了更好的代码可维护性和异常处理能力,十分的歪歪古德
总结一下 CompletableFuture 核心概念
▼java复制代码// 1. 异步执行 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "异步任务结果"; }); // 2. 等待完成 CompletableFuture.allOf(future1, future2, future3); // 3. 获取结果 String result = future.join(); // 4. 异常处理 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { return riskyOperation(); } catch (Exception e) { return "错误信息"; } });
线程上下文管理
▼java复制代码// 为什么需要线程上下文管理? // 因为每个线程都有自己的上下文,主线程的上下文不会自动传递给子线程 // 正确的做法: CompletableFuture.supplyAsync(() -> { try { // 在子线程中设置上下文 WmsLoginUtil.setDefaultWhId(whId); return businessLogic(); } finally { // 清理上下文 WmsLoginUtil.setDefaultWhId(null); } });
上班摸鱼写的文章有点乱
