Java 一个简单的并行加载对象属性的工具类
对于一些属性比较多的 DTO 或者 VO 特别是有些属性要请求其他服务获取的时候,如果是串行加载的话,一般都会比较耗时。当然也可以选择并行的方式实现,示例代码如下:
/*
* @auth JerryZeng
*/
class ReimburseDTO {
/*
* 报销人 id
*/
String reimEmployeeId;
/*
* 成本中心 id
*/
String costCenterId;
/*
* 报销费用 id
*/
String expense;
}
/*
* @auth JerryZeng
*/
class ReimburseVO {
/*
* 报销人
*/
Employee reimEmployee;
/*
* 成本中心
*/
Department costCenter;
/*
* 报销费用
*/
Expnese expense;
// getter, setter 省略
}
ReimburseVO convert(ReimburseDTO dto) {
ReimburseVO res = new ReimburseVO();
List<CompletableFuture> futureList = new ArrayList<>();
futureList.add(CompletableFuture.runAsync(() -> {
res.setReimEmployee(employeeService.getById(dto.getReimEmployeeId()));
}));
futureList.add(CompletableFuture.runAsync(() -> {
res.setCostCenter(departmentService.getById(dto.getCostCenterId()));
}));
futureList.add(CompletableFuture.runAsync(() -> {
res.setExpense(expenseService.getById(dto.getExpenseId()));
}));
CompletableFuture all = CompletableFuture.allOf(futureList.toArray(futureList.toArray(new CompletableFuture[0])));
try {
all.get(1, TimeUnit.SECONDS);
} catch (Execption e) {
// 异常处理
}
// 其他业务处理
return res;
}
在日常使用 CompletableFuture 需要注意的是,它所使用的线程池默认是 ForkJoinPool.commonPool() ,核心线程数为操作系统的核心数减1。它其实适用的就是计算密集型的操作,但是我们日常使用中大部分的都是业务操作,都是 IO 密集型的。所以要我们传入一个事先定义好的线程池作为参数,使用如下的方法(JDK21 CompletableFuture#Line2031):
/**
* Returns a new CompletableFuture that is asynchronously completed
* by a task running in the given executor after it runs the given
* action.
*
* @param runnable the action to run before completing the
* returned CompletableFuture
* @param executor the executor to use for asynchronous execution
* @return the new CompletableFuture
*/
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
这样的话使用 CompletableFuture.runAsync 就会变得更臃肿且麻烦。如果我们要在多个地方进行这种对象的加载的话,很多这种重复的代码就不优雅了,所以我们可以尝试将重复的代码封装起来,让我们更多的精力放在业务代码的编辑上,封装的代码如下:
public class DataFetcher<D> {
private final D data;
private final List<Node<?>> nodes;
private static final Executor DEFAULT_EXECUTOR;
private static final int DEFAULT_TIME_WAIT_SECONDS = 10;
public DataFetcher(D data) {
this.data = data;
this.nodes = new ArrayList<>();
}
public void addNode(Node<?> node) {
Objects.requireNonNull(node);
this.nodes.add(node);
}
public <P> void addNode(Supplier<P> fetchFunction, BiConsumer<D, P> setter) {
this.nodes.add(new Node<>(fetchFunction, setter));
}
public void fetch() {
fetch(DEFAULT_TIME_WAIT_SECONDS, DEFAULT_EXECUTOR);
}
public void fetch(int timeWaitSeconds) {
fetch(timeWaitSeconds, DEFAULT_EXECUTOR);
}
public void fetch(int timeWaitSeconds, Executor executor) {
List<CompletableFuture<?>> futures = nodes.stream()
.map(node -> CompletableFuture.runAsync(node::fetch, executor))
.collect(Collectors.toList());
try {
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).get(timeWaitSeconds, TimeUnit.SECONDS);
nodes.forEach(Node::propSetter);
} catch (Exception e) {
throw new RuntimeException("Failed to fetch, causes " + e.getMessage(), e);
}
}
public class Node<Prop> {
private Prop p;
private final Supplier<Prop> fetchFunction;
private final BiConsumer<D, Prop> setter;
public Node(Supplier<Prop> fetchFunction, BiConsumer<D, Prop> setter) {
this.fetchFunction = fetchFunction;
this.setter = setter;
}
void fetch() {
p = this.fetchFunction.get();
}
void propSetter() {
this.setter.accept(data, p);
}
}
static {
int availableProcessors = Runtime.getRuntime().availableProcessors();
DEFAULT_EXECUTOR = new ThreadPoolExecutor(
availableProcessors * 2 + 1,
availableProcessors * 3,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2048),
new DefaultThreadFactory());
}
private static class DefaultThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "dataFetcher-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
}
使用这个工具类我们就可以把上诉的代码改为:
ReimburseVO convert(ReimburseDTO dto) {
ReimburseVO res = new ReimburseVO();
DataFetcher<ReimburseVO> fetcher = new DataFetcher<>();
fetcher.addNode(() -> employeeService.getById(dto.getReimEmployeeId()), res::setReimEmployee);
fetcher.addNode(() -> departmentService.getById(dto.getCostCenterId()), res::setCostCenter);
fetcher.addNode(() -> expenseService.getById(dto.getExpenseId()), res::setExpense);
fetcher.featch();
// 其他业务处理
return res;
}
这样的抽象虽然难度不高,但是从日常编码的重复点出发,有意识的训练自己的抽象能力,也算是一种锻炼了。工具类代码地址:github.com/BluebuleZJR…