记一次线程池使用不当造成死锁的问题

347 阅读1分钟

在工作中发现了一个有意思的Bug,这里记录一下,大致的逻辑代码如下:

public Object outMethod(){
    List<CompletableFuture<Object>> futures = new ArrayList<>(10);
    for(int i=0;i<10;i++){
        CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
            Object data = interMethod(); //关键方法
            return data;
        });
        futures.add(future);
    }
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // 3 
}
public Object interMethod() {
    List<CompletableFuture<Object>> futures = new ArrayList<>(10);
    for (int i = 0;i<10 i++) {
        CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
            // some logic
            return object;
        });
        futures.add(future);
    }
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}

在执行outMethod方法中调用了 interMethod然后在 interMethod中又使用到了CompletableFuture,我们知道CompletableFuture的supplyAsync()方法是将任务提交给ForkJoinPool.commonPool()线程池去执行的。

上面代码产生死锁的原因是:ForkJoinPool.commonPool()的线程数默认是 CPU数量-1,在OutMethod执行的过程中可能会消耗完所有的线程数,然后阻塞在 //3步上,等待任务执行完成,但是在执行内部的interMethod()此时线程池中没有线程能执行,导致interMethod被阻塞,等待outMethod()方法执行完毕,释放线程,而outMethod方法需要interMethod方法执行完毕才会返回结果释放线程,所以outMethod方法和interMethod形成了循环等待就形成了死锁。

解决方案:定义两个不同的线程池,分别执行对应的outMethod和interMethod即可。