代码里有这样一段需求,先去取得 approval ,再去进行 deploy, 采取了多线程运行,代码运行上去之后,发现线程积压严重。请求量并不大,但是系统就hung在那里。 

线程的饥饿_线程

 

发现代码出现问题的大概是这段逻辑

 

 

 

CompletableFuture<List<Map<String, Object>>> manifestListResultFuture =
        CompletableFuture.supplyAsync(() -> manifestService.getManifests(cluster, ns) , forkJoinPool)
                .thenApply(manifestListResult -> {
                      
                     //do some logic
                });

  

Stream<ManifestInfo> manifestInfoStream =  manifestListResultFuture.get().parallelStream().map(this::convert)
                .map(manifestInfo -> combineApprovals(manifestInfo,approvalCrsFuture));
            if(NeedApprovalFor(paasRealm)){
                manifestInfoStream = manifestInfoStream.filter( manifestInfo -> ManifestService.isApprovedFor(manifestInfo, paasRealm));
            }
            final var targetStream = manifestInfoStream;
            return forkJoinPool.submit( () -> targetStream.sorted(sortByCreateTimeDesc)
                    .map(manifestInfo -> combinePackagesMeta(manifestInfo, imageCrsFuture))
                    .collect(toList())).get();

 

 

这段逻辑为什么会导致线程阻塞呢? 从代码上分析这是一个典型线程池饥饿的问题。上面的代码有点复杂,咱们看一个简单的例子

public class TestStarvation {
                private static final Logger log = LoggerFactory.getLogger(TestStarvation.class);

    // return deploy result
    static String deploy() {
        return "deploying"
    }
   
    public static void main(String[] args) {
        //fixed thread
        ExecutorService waiterPool = Executors.newFixedThreadPool(2);
 
        waiterPool.execute(() -> {
            log.debug("get Approval...");
           
            //deploy in another thread
            Future<String> f = waiterPool.submit(() -> {
                log.debug("deploy");
                return deploy();
            });
           
            try {
                log.debug("deploy result: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        waiterPool.execute(() -> {
            log.debug("get Approval..");
            Future<String> f = waiterPool.submit(() -> {
                log.debug("deploy");
                return deploy();
            });
            try {
                log.debug("deploy result: {}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
 
    }
}

  

当然你运行这段代码,发现thread就会hung住, 原因是线程池就2个资源, 都被getApproval 抢占了,在方法内层的 deploy 由于和getApproval用的是同一个线程池,所以它需要等getApproval 释放资源,但是getApproval 需要等待内层的deploy 做完才能释放资源。造成了内层的线程饥饿。

当然上面是个极端例子,我们真实的case 中的原理也是一样,他们用了同一个线程池,而且他们的调用有嵌套,造成了大量的等待。

处理线程嵌套的模式都是使用不同的线程池,来保证内部和外部没有竞争。知道了原因我们再定义一个fixed thread pool executorSerice, 把它作为  manifestListResultFuture 运行的线程池,这个问题就解决了。

 

CompletableFuture<List<Map<String, Object>>> manifestListResultFuture =
        CompletableFuture.supplyAsync(() -> manifestService.getManifests(cluster, ns) , executorSerice)
                .thenApply(manifestListResult -> {
                      
                     //do some logic
                });