CompletableFuture
是 Java 8 引入的一个用于异步编程的类,它是 Future 接口的增强版本。以下是主要特点和常用方法:
- 基础创建:
// 创建一个完成的 Future
CompletableFuture<String> cf1 = CompletableFuture.completedFuture("Hello");
// 创建一个异步执行的 Future
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
return "Hello Async";
});
- 转换和链式操作:
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World") // 转换值
.thenAccept(System.out::println) // 消费值
.thenRun(() -> System.out.println("Done")); // 执行操作
- 组合多个 Future:
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> "World");
// 组合两个 Future
cf1.thenCombine(cf2, (result1, result2) -> result1 + " " + result2)
.thenAccept(System.out::println);
- 异常处理:
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("出错了");
})
.exceptionally(throwable -> {
System.out.println("发生错误:" + throwable.getMessage());
return "默认值";
})
.thenAccept(System.out::println);
- 实际应用示例:
public class Service {
public CompletableFuture<String> getDataAsync() {
return CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "数据";
});
}
public void process() {
getDataAsync()
.thenApply(data -> data + "处理")
.thenAccept(result -> System.out.println("结果: " + result))
.exceptionally(ex -> {
System.err.println("处理失败: " + ex.getMessage());
return null;
});
}
}
主要方法说明:
supplyAsync()
: 异步执行有返回值的任务runAsync()
: 异步执行无返回值的任务thenApply()
: 转换值(类似 map)thenAccept()
: 消费值(无返回值)thenCombine()
: 组合两个 FutureallOf()
: 等待多个 Future 全部完成anyOf()
: 等待多个 Future 中的任意一个完成
使用建议:
- 优先使用
thenCompose()
而不是thenApply()
来组合返回 CompletableFuture 的方法 - 记得处理异常,可以使用
exceptionally()
或handle()
- 如果需要自定义线程池,可以在创建时指定:
CompletableFuture.supplyAsync(supplier, executor)
这些是 CompletableFuture 的基础用法,它能大大简化异步编程的复杂度。需要注意的是,CompletableFuture 默认使用 ForkJoinPool.commonPool(),在生产环境中可能需要考虑使用自定义的线程池。
项目场景
批量修改信息
1)请求类,接受图片 id 列表等字段:
@Data
public class PictureEditByBatchRequest implements Serializable {
/**
* 图片 id 列表
*/
private List<Long> pictureIdList;
/**
* 空间 id
*/
private Long spaceId;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
private static final long serialVersionUID = 1L;
}
2)开发批量修改图片服务,依次完成参数校验、空间权限校验、图片查询、批量更新操作:
@Override
@Transactional(rollbackFor = Exception.class)
public void editPictureByBatch(PictureEditByBatchRequest pictureEditByBatchRequest, User loginUser) {
List<Long> pictureIdList = pictureEditByBatchRequest.getPictureIdList();
Long spaceId = pictureEditByBatchRequest.getSpaceId();
String category = pictureEditByBatchRequest.getCategory();
List<String> tags = pictureEditByBatchRequest.getTags();
// 1. 校验参数
ThrowUtils.throwIf(spaceId == null || CollUtil.isEmpty(pictureIdList), ErrorCode.PARAMS_ERROR);
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
// 2. 校验空间权限
Space space = spaceService.getById(spaceId);
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
if (!loginUser.getId().equals(space.getUserId())) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "没有空间访问权限");
}
// 3. 查询指定图片,仅选择需要的字段
List<Picture> pictureList = this.lambdaQuery()
.select(Picture::getId, Picture::getSpaceId)
.eq(Picture::getSpaceId, spaceId)
.in(Picture::getId, pictureIdList)
.list();
if (pictureList.isEmpty()) {
return;
}
// 4. 更新分类和标签
pictureList.forEach(picture -> {
if (StrUtil.isNotBlank(category)) {
picture.setCategory(category);
}
if (CollUtil.isNotEmpty(tags)) {
picture.setTags(JSONUtil.toJsonStr(tags));
}
});
// 5. 批量更新
boolean result = this.updateBatchById(pictureList);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
}
上述是调用mybatis plus
的 updateBatchById
批量更新的操作。如果要处理大量数据,可以使用线程池 + 分批 + 并发进行优化,参考代码如下:
@Resource
private ThreadPoolExecutor customExecutor;
/**
* 批量编辑图片分类和标签
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void batchEditPictureMetadata(PictureBatchEditRequest request, Long spaceId, Long loginUserId) {
// 参数校验
validateBatchEditRequest(request, spaceId, loginUserId);
// 查询空间下的图片
List<Picture> pictureList = this.lambdaQuery()
.eq(Picture::getSpaceId, spaceId)
.in(Picture::getId, request.getPictureIds())
.list();
if (pictureList.isEmpty()) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "指定的图片不存在或不属于该空间");
}
// 分批处理避免长事务
int batchSize = 100;
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int i = 0; i < pictureList.size(); i += batchSize) {
List<Picture> batch = pictureList.subList(i, Math.min(i + batchSize, pictureList.size()));
// 异步处理每批数据
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
batch.forEach(picture -> {
// 编辑分类和标签
if (request.getCategory() != null) {
picture.setCategory(request.getCategory());
}
if (request.getTags() != null) {
picture.setTags(String.join(",", request.getTags()));
}
});
boolean result = this.updateBatchById(batch);
if (!result) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "批量更新图片失败");
}
}, customExecutor);
futures.add(future);
}
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
示例图如下