使用CompletableFuture遇到的坑
经典用法allof()
最近在做项目时,需要用到多线程,平时用的多的是callable用future来接收返回值,用线程池提交异步任务,或者countDownLatch来实现,具体会在别的章节里展开,突然间想起海哥有一次提到了CompletableFuture,就拿来体验一下,果然是有坑。
不废话上代码
static void test2() {
List<Long> sqrLongs = new ArrayList<>();
List<Long> longs = Arrays.asList(12L, 13L, 14L, 15L, 16L);
List<CompletableFuture> completableFutures = Lists.newArrayList();
for (int i = 0; i < 5; i++) {
int finalI = i;
CompletableFuture completableFuture = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Long aLong = longs.get(finalI);
sqrLongs.add(aLong * aLong);
log.info("异步线程=======>" + Thread.currentThread().getName() + "-" + System.currentTimeMillis());
}, excecuter);
completableFutures.add(completableFuture);
}
CompletableFuture<Void> allFuture = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()]));
try {
log.info("get前=======>" + System.currentTimeMillis());
allFuture.get();
log.info("get后=======>" + System.currentTimeMillis());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
log.info("主线程=======>" + System.currentTimeMillis());
sqrLongs.stream().forEach(System.out::println);
}
- 这里只是简单的算一个平方值,main方法运行一下,
public static void main(String[] args) throws InterruptedException {
test2();
}
- 输出结果:
- CompletableFuture.allOf()会聚合出一个新的CompletableFuture,执行get()会阻塞当前主线程,异步执行后,结果出现了null值,视觉上是多线程没返回结果,猜测是线程超时?但是根本没有费时的操作,也没有设置超时时间。分析日志,pool-2-thread-1到5,全部是同一时刻执行完毕,联想到多线程,list是不安全的类。
修改代码为安全的类
//注意⚠️这里使用线程安全的类
List<Long> sqrLongs = new Vector<>();
运行结果:
全部正常。
总结分析
join会阻塞主线程,直到所有线程都返回,这里使用的是自己创建的线程池,如果使用的是默认的线程池ForkJoinPool,会把主线程设置成守护线程,如果配合使用.whenComplete()不会阻塞主线程,主线程运行结束,异步自动退出,不会等待异步结束。所以,多线程的使用要求我们必须具备扎实的技术知识,在对一个Api底层原理不够清晰的情况下,还是容易出现问题的,正所谓:知己知彼,百战不殆。allof得使用线程安全的类,才能保证所有结果全部正常写入。