情况是这样的,我们的APP首页,加载的数据非常多,由于业务上对展示数据实时性的要求,不便于使用缓存,所以考虑用并行化查询来优化首页接口的响应速度,使用
CompletableFuture
,此类的具体使用就不赘述了。
基于CompletableFuture本身的一些缺点,采用包装类规范使用尽可能地来避免,以下是我写的包装类
CompletableFutures
,具体使用看executeAll
方法的注释即可。
注:没有考虑某一个任务超时的情况,我们没有这种场景,就像有些公司一天几百单,非要搞多服务之间的分布式事务,考虑用bigint作为自增id是否会越界,我觉得,想多了。
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
/**
* 对CompletableFuture的包装
* 使用CompletableFuture的问题如下:
* 1.尽量使用异步方法,及即带Async后缀的方法,否则使用CompletableFuture意义不大。
* <p>
* 2.使用自定义线程池,当supplyAsync不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)。
* 本类创建对象要求必传Executor,不存在此问题,自行考量Executor配置。
* <p>
* 3.supplyAsync内部嵌套CompletableFuture.supplyAsync, 使用同一个Executor, 导致死锁。
* supplyAsync向Executor请求线程,并且内部子任务又向Executor请求线程。
* Executor大小为10,当同一时刻有10个请求到达,则Executor被打满,子任务请求线程时进入阻塞队列排队,但是父任务的完成又依赖于子任务,这时由于子任务得不到线程,父任务无法完成。
* 主线程执行join()进入阻塞状态,并且永远无法恢复。
* 本类只接受Callable,不建议任务里面再嵌套子任务。
* <p>
* 4.CompletableFuture在回调方法中对异常进行了包装。大部分异常会封装成CompletionException后抛出,真正的异常存储在cause属性中。
* 本类中已处理
*/
@Slf4j
public class CompletableFutures {
private final ExecutorService executor;
private final String logId;
private final List<TaskWrapper<?>> taskWrapperList;
private CompletableFutures(ExecutorService executor, String logId) {
this.executor = executor;
this.logId = logId;
this.taskWrapperList = new ArrayList<>();
}
public static CompletableFutures of(@NonNull ExecutorService executor, @NonNull String logId) {
return new CompletableFutures(executor, logId);
}
/**
* 添加任务
* @param task task的返回值不能为null,如果不需要返回值,可return 1 或 return true
* @return TaskWrapper 返回任务的包装类,执行完毕后用于获取结果
*/
public <T> TaskWrapper<T> addTask(Callable<T> task) {
TaskWrapper<T> taskWrapper = new TaskWrapper<>(task);
this.taskWrapperList.add(taskWrapper);
return taskWrapper;
}
/**
* 提交全部任务,需要保证 tasks 和 results 按顺序一一对应
* 使用示例如下:
* CompletableFutures wrapper = CompletableFutures.of(executor, logId);
* <p>
* CompletableFutures.TaskWrapper<Integer> taskWrapper1 = wrapper.addTask(() -> 1);
* CompletableFutures.TaskWrapper<String> taskWrapper2 = wrapper.addTask(() -> "2");
* <p>
* wrapper.executeAll();
* <p>
* Integer result1 = taskWrapper1.getResult();
* String result2 = taskWrapper2.getResult();
*/
public void executeAll() {
if (this.taskWrapperList.isEmpty()) {
log.info("CompletableFutures#submitAllTask futureTaskWrappers size is zero");
return;
}
// 执行所有任务,并填充到completableFutureList
log.info("CompletableFutures#submitAllTask before supplyAsync tasks...");
List<CompletableFuture<?>> completableFutureList = new ArrayList<>();
for (TaskWrapper<?> taskWrapper : this.taskWrapperList) {
CompletableFuture<?> completableFuture = supplyAsync(taskWrapper.getTask());
completableFutureList.add(completableFuture);
}
log.info("CompletableFutures#submitAllTask finished supplyAsync tasks...");
// 等待所有任务执行完毕
waitAllOf(completableFutureList);
log.info("CompletableFutures#submitAllTask All tasks have been completed...");
// 填充结果
int size = completableFutureList.size();
for (int i = 0; i < size; i++) {
this.taskWrapperList.get(i).setResult(completableFutureList.get(i).join());
}
log.info("CompletableFutures#submitAllTask All results have been filled...");
}
/**
* 提交任务
*/
public <T> CompletableFuture<T> supplyAsync(@NonNull Callable<T> callable) {
// 包装任务,设置logId
Supplier<T> supplierInner = () -> {
LogIdThreadLocal.setLogId(this.logId);
T t;
try {
t = callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
LogIdThreadLocal.clean();
}
return t;
};
// 提交包装后的任务进入线程池, 设置异常处理方式
return CompletableFuture
.supplyAsync(supplierInner, this.executor)
.exceptionally((ex) -> {
if (ex.getCause() != null) {
throw new RuntimeException(ex.getCause());
} else {
throw new RuntimeException(ex);
}
});
}
/**
* 阻塞等待全部任务执行完毕
*
* @param completableFutures CompletableFuture的集合
*/
public void waitAllOf(Collection<CompletableFuture<?>> completableFutures) {
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();
}
/**
* 包装任务,组装返回值
*/
public static class TaskWrapper<T> {
private T result;
private final Callable<T> task;
private TaskWrapper(@NonNull Callable<T> task) {
this.task = task;
}
private Callable<T> getTask() {
return task;
}
private void setResult(Object result) {
@SuppressWarnings("unchecked") T t = (T) result;
this.result = t;
}
/**
* 赋值后才可执行
*/
public T getResult() {
if (result == null) {
throw new UnsupportedOperationException("当前任务未经处理,请正确使用本方法");
}
return result;
}
}
}
import java.util.UUID;
public class LogIdThreadLocal {
public static final ThreadLocal<String> logId = new ThreadLocal<String>();
public static String getLogId() {
return logId.get();
}
public static void setLogId(String logIdStr) {
logId.set(logIdStr);
}
public static void clean() {
logId.set(null);
logId.remove();
}
}