如何使用CompletableFuture批量处理Name对象异步校验任务?
用CompletableFuture实现批量异步名称校验的正确方式
要实现批量异步校验名称可用性、等所有任务完成后返回结果的需求,你需要调整异步方法的返回类型,并修改调用逻辑,具体步骤如下:
1. 改造异步处理方法
把原来返回Name的异步方法改成返回CompletableFuture<Name>,这样才能追踪异步任务的执行状态和结果:
@Async("nameAvailabilityExecutor") public CompletableFuture<Name> isNameAvailable(Name nameObj) { String name = nameObj.getName(); // 建议创建新的Name对象返回,避免修改原对象引发并发问题 Name checkedName = new Name(); BeanUtils.copyProperties(nameObj, checkedName); checkedName.setAvailable(getByNameCheck(name)); return CompletableFuture.completedFuture(checkedName); }
如果业务允许直接修改传入的nameObj,也可以直接操作原对象,但如果原对象在其他线程被引用,可能出现数据不一致的问题,优先推荐返回新对象。
2. 修改服务类的批量调用逻辑
原来的循环同步调用根本没用到异步的优势,得改成批量提交异步任务,等所有任务完成后再收集结果:
public LinkedHashSet<Name> isNameAvailable(LinkedHashSet<Name> nameList) { // 先把所有异步任务提交到线程池,收集对应的Future List<CompletableFuture<Name>> futureTasks = new ArrayList<>(); for (Name name : nameList) { futureTasks.add(domainAvailabilityServiceThread.isNameAvailable(name)); } // 等待所有异步任务执行完成 CompletableFuture<Void> allDone = CompletableFuture.allOf( futureTasks.toArray(new CompletableFuture[0]) ); // 所有任务完成后,统一收集结果 CompletableFuture<LinkedHashSet<Name>> resultFuture = allDone.thenApply(ignored -> { LinkedHashSet<Name> checkedList = new LinkedHashSet<>(); for (CompletableFuture<Name> future : futureTasks) { try { checkedList.add(future.get()); } catch (InterruptedException | ExecutionException e) { // 这里处理单个任务的异常,比如打日志、标记该名称校验失败 e.printStackTrace(); // 示例:如果任务失败,返回一个标记为不可用或错误的Name对象 // Name errorName = new Name(); // errorName.setName(...); // errorName.setAvailable(false); // errorName.setHasError(true); // checkedList.add(errorName); } } return checkedList; }); // 阻塞获取最终结果(因为当前方法需要同步返回结果,所以必须阻塞) try { return resultFuture.get(); } catch (InterruptedException | ExecutionException e) { // 处理全局异常,比如抛出业务异常让上层处理 throw new RuntimeException("批量名称校验任务执行失败", e); } }
核心要点说明
- 异步方法返回值:
@Async方法必须返回CompletableFuture(或Future),如果直接返回Name,Spring的代理会直接返回null或者强制同步执行,根本起不到异步效果。 - 等待全部任务:
CompletableFuture.allOf()会等待所有传入的异步任务完成,它返回的Future完成时,所有子任务都已经结束。 - 结果收集时机:必须在所有任务完成后再统一收集结果,不能在异步任务中直接往
LinkedHashSet加元素——因为LinkedHashSet不是线程安全的,并发修改会出问题。 - 异常处理:一定要捕获
get()方法抛出的异常,单个任务失败不应该导致整个批量任务崩溃,根据业务需求处理异常场景(比如标记该名称校验失败)。
内容的提问来源于stack exchange,提问作者Ar_
相关产品推荐
相关产品推荐

