CompletableFuture、CompletionService使用
文章目录
1. 简介
大神 Doug Lea在java8 lambda表达式基础上,开发的可以用于串行、处理异常、返回结果的接口
主要的方法
runAsync
:由于使用的是 Runnable 函数式表达式,自然也不会获取到结果supplyAsync
: 想获取异步计算的返回结果需要使用supplyAsync()
方法
这里重点说下第二种
2. supplyAsync
看一个简单例子
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
log.info("运行在一个单独的线程当中");
return "我有返回值";
});
log.info(future.get());
处理多个CompletableFuture
然后汇总结果
- 建立多个
CompletableFuture
- 使用allof汇总
假设有这么个场景,根据id集合查询数据,需要分批查,则可以这样
//将ids划分为子集合
List<List<Long>> lists = Lists.partition(ids, partitionSize);
//创建多个 CompletableFuture
List<CompletableFuture<List<User>>> completableFutures = Lists.newArrayList();
for (List<Long> list : lists) {
CompletableFuture<List<User>> one = CompletableFuture
.supplyAsync(()-> userDao.queryByIds(list);
completableFutures.add(one);
}
//汇总结果
CompletableFuture<Void> allChild = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()]));
//获取结果
CompletableFuture<List<List<ServiceSummaryPo>>> tempData = allChild.thenApply(one->{
return completableFutures.stream().map(p->p.get()).collect(Collectors.toList());
}).exceptionally(ex -> { //这里是处理异常
log.error("错误--" + ex.getMessage());
return Lists.newArrayList();
});
List<List<User>> result = tempData.get();
anyOf
线程任一个返回就可以,场景后续补充
神器CompletionService
场景:有4个耗时任务 A、B、C、D。 每个任务的执行时间随入参变化而变化
传统方案:用ExecutorService创建线程池,提交任务到线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future> futures = new ArrayList<Future<Integer>>();
futures.add(executorService.submit(A));
futures.add(executorService.submit(B));
futures.add(executorService.submit(C));
futures.add(executorService.submit(D));
// 遍历 Future list,通过 get() 方法获取每个 future 结果
for (Future future:futures) {
Integer result = future.get();
// 其他业务逻辑
}
先直入主题,用 CompletionService 实现同样的场景
ExecutorService executorService = Executors.newFixedThreadPool(4);
// ExecutorCompletionService 是 CompletionService 唯一实现类
CompletionService executorCompletionService= new ExecutorCompletionService<>(executorService );
List<Future> futures = new ArrayList<Future<Integer>>();
futures.add(executorCompletionService.submit(A));
futures.add(executorCompletionService.submit(B));
futures.add(executorCompletionService.submit(C));
futures.add(executorCompletionService.submit(D));
// 遍历 Future list,通过 get() 方法获取每个 future 结果
for (int i=0; i<futures.size(); i++) {
Integer result = executorCompletionService.take().get();
// 其他业务逻辑
}
两种方式在代码实现上几乎一毛一样,我们曾经说过 JDK 中不会重复造轮子,如果要造一个新轮子,必定是原有的轮子在某些场景的使用上有致命缺陷
注意
第一种方式的实现:依照提交顺序,阻塞获取结果,什么意思呢?
按照程序的执行顺序,程序在 get() 任务 A 的执行结果会阻塞在那里,导致任务 B,C,D 的后续任务没办法执行。又因为每个任务执行时间是不固定的,所以无论怎样调整将任务放到 List 的顺序,都不合适,这就是致命弊端
新轮子自然要解决这个问题,它的设计理念就是哪个任务先执行完成,get() 方法就会获取到相应的任务结果,这么做的好处是什么呢?来看个图你就瞬间理解了
两张图一对比,执行时长高下立判了,在当今高并发的时代,这点时间差,在吞吐量上起到的效果可能不是一点半点了
第二种方式是注册一个回调函数,谁先执行完,就通知,就先拿到
可以推广的场景
- 多线程批量查询sql,优化时间
- 多线程写Excel