如何避免递归可调用的执行器服务的拥塞/停滞/死锁

ExecutorService中的所有线程都在忙于等待等待执行器服务队列中阻塞的任务的任务。

示例代码:

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。

chen362015 回答:如何避免递归可调用的执行器服务的拥塞/停滞/死锁

您应该使用ForkJoinPool。正是出于这种情况。

尽管您的解决方案在等待子任务完成时永久阻塞线程,但是窃取ForkJoinPool的工作可以在join()中执行工作。对于您可能正在运行的小型(通常是递归)可变数量的此类情况,这使它高效。对于常规线程池,您需要加大其大小,以确保不会耗尽线程。

使用CompletableFuture时,您需要自己进行大量实际的计划/安排,如果您决定进行更改,则调整会更加复杂。使用FJP时,您唯一需要调整的是池中的线程数量,使用CF时,您还需要考虑thenthenAsync。>

,

我建议尝试通过CompletableFuture来分解工作以使用完成阶段

CompletableFuture.supplyAsync(outerTask) .thenCompose(CompletableFuture.allOf(innerTasks)

这样,您的外部任务就不会在处理内部任务时占用执行线程,但是您仍然可以获得一个Future,它可以在完成整个工作后解决。如果这些阶段耦合得太紧,则很难将它们拆分开。

,

您建议的方法基本上是基于这样的假设:如果线程数大于任务数,则可能存在解决方案,如果您已经分配了一个线程池,则此方法将不起作用。您可以尝试查看。正如您在代码注释中所述,这是死锁的简单情况。

在这种情况下,请使用两个单独的线程池,一个用于外部,另一个用于内部。当内部池中的任务完成时,只需将值返回给外部即可。

或者您可以简单地动态创建一个线程,完成其中的工作,获取结果,然后将其返回给外部。

本文链接:https://www.f2er.com/3119769.html

大家都在问