在CPU核心数小于两核的机器上使用CompletableFuture时要注意,最好自定义线程池,否则会降级成新建线程,引发不必要的问题。
通过CompletableFuture.runAsync方法的源码可以看出,如果不提供线程池入参,则默认使用的是CompletableFuture.ASYNC_POOL这个线程池。
private static final boolean USE_COMMON_POOL =
(ForkJoinPool.getCommonPoolParallelism() > 1);
/**
* Default executor -- ForkJoinPool.commonPool() unless it cannot
* support parallelism.
*/
private static final Executor ASYNC_POOL = USE_COMMON_POOL ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
当ForkJoinPool的并行度大于1时,才会使用ForkJoinPool.commonPool(),否则使用的是ThreadPerTaskExecutor
static final class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
Objects.requireNonNull(r);
new Thread(r).start();
}
}
而ThreadPerTaskExecutor是一个虚假的线程池,每次都是直接new一个线程执行任务的,而坑就在这里。因为ForkJoinPool的并行度默认是等于机器的CPU核心数-1,所以当在双核CPU的机器环境下执行时,就会导致每次都是新建线程执行任务。