java如何避免使用递归可调用的executorservice阻塞/暂停/死锁
executor服务中的所有线程都忙于等待被卡在executor服务队列中的任务
示例代码:
ExecutorService es=Executors.newFixedThreadPool(8);
Set<Future<Set<String>>> outerSet=new HashSet<>();
for(int i=0;i<8;i++){
outerSet.add(es.submit(new Callable<Set<String>>() {
@Override
public Set<String> call() throws Exception {
Thread.sleep(10000); //to simulate work
Set<Future<String>> innerSet=new HashSet<>();
for(int j=0;j<8;j++) {
int k=j;
innerSet.add(es.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "number "+k+" in inner loop";
}
}));
}
Set<String> out=new HashSet<>();
while(!innerSet.isEmpty()) { //we are stuck at this loop because all the
for(Future<String> f:innerSet) { //callable in innerSet are stuckin the queue
if(f.isDone()) { //of es and can't start since all the threads
out.add(f.get()); //in es are busy waiting for them to finish
}
}
}
return out;
}
}));
}
除了为每一层创建更多的线程池或者拥有一个大小不固定的线程池之外,还有什么方法可以避免这种情况吗
一个实际的例子是,如果将一些可调用项提交到ForkJoinPool。commonPool(),然后这些任务使用的对象也会在其方法中提交到commonPool
# 1 楼答案
您应该使用
ForkJoinPool
。它是为这种情况而设计的虽然您的解决方案在线程等待其子任务完成时永久阻止线程,但工作
ForkJoinPool
可以在join()
中执行工作。这使得它在运行的小型(通常是递归)任务数量可变的情况下非常有效。对于常规线程池,您需要对其进行扩容,以确保不会耗尽线程使用
CompletableFuture
时,您需要自己处理更多的实际计划/日程安排,如果您决定更改内容,则调整将更加复杂。使用FJP
时,您唯一需要调整的是池中线程的数量,使用CF
时,您还需要考虑then
与thenAsync
# 2 楼答案
我建议尝试通过
CompletableFuture
分解工作以使用完成阶段CompletableFuture.supplyAsync(outerTask) .thenCompose(CompletableFuture.allOf(innerTasks)
这样,您的外部任务在处理内部任务时不会占用执行线程,但您仍然可以在整个任务完成后获得解决方案。但是,如果这些阶段的耦合过于紧密,就很难将它们分开
# 3 楼答案
您所建议的方法基本上是基于这样一个假设的,即如果线程数大于任务数,则可能存在解决方案。如果您已经分配了单个线程池,则该方法在这里将不起作用。你可以试试看。正如您在代码注释中所述,这是一个简单的死锁情况
在这种情况下,使用两个单独的线程池,一个用于外部,另一个用于内部。当来自内部池的任务完成时,只需将值返回给外部池
或者您可以简单地动态创建一个线程,在其中完成工作,得到结果并将其返回到外部