线程的饥饿
代码里有这样一段需求,先去取得 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
});
浙公网安备 33010602011771号