先说结论:
假如有20CompletableFuture任务并发执行时,都使用默认线程池ForkJoinPool,但cpu的核心数又小于3,那么就会新建20个线程(不使用默认线程池了),这20个线程相互竞争cpu资源和内存,很多线程都在等待,浪费了大量的性能在线程上下文切换上。
线程池大小设定:
从runAsync
方法点进去
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println(1);
});
可以看见使用的是asyncPool
。点进asyncPool
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
useCommonPool
是否为true决定了使用 ForkJoinPool线程池还是新建一个线程池。点进useCommonPool。
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
这里判定的是ForkJoinPool common线程池中并行度级别是否大于1。点进 getCommonPoolParallelism() 方法
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
返回的是commonParallelism
这个字段,再往下找。
public static int getCommonPoolParallelism() {
return commonParallelism;
}
发现只有一个地方对这个属性进行赋值,继续。
static final int commonParallelism;
发现commonParallelism 由par决定,par来自common.config SMASK做与运算。SMASK定义为0xffff(65535),common.config由 makeCommonPool()得到。点进makeCommonPool()方法
...
static final ForkJoinPool common;
static {
...
common = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ForkJoinPool>() {
public ForkJoinPool run() {
return makeCommonPool(); //common的config由此方法返回
}});
int par = common.config & SMASK; // report 1 even if threads disabled
commonParallelism = par > 0 ? par : 1;
}
我简化了下面源码,parallelism
初始化为 -1,若jvm启动参数有java.util.concurrent.ForkJoinPool.common那么parallelism将会被启动参数指定。Runtime.getRuntime().availableProcessors() 是获取虚拟机可使用的处理器数量。在jvm未定义参数的前提下,处理器数量若小于等于2,那么并行度parallelism就为1,反之则为 (处理器数量 - 1)
。定义了jvm参数,若参数值大于MAX_CAP(32767),则重新赋值。即parallelism 总会大于0, 继续点进ForkJoinPool的构造方法。
private static ForkJoinPool makeCommonPool() {
...
int parallelism = -1;
try { // ignore exceptions in accessing/parsing properties
String pp = System.getProperty
("java.util.concurrent.ForkJoinPool.common.parallelism");
if (pp != null)
parallelism = Integer.parseInt(pp);
} catch (Exception ignore) {
}
if (parallelism < 0 && // default 1 less than #cores
(parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
parallelism = 1;
if (parallelism > MAX_CAP)
parallelism = MAX_CAP;
return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE, // 注意 LIFO_QUEUE等于0
"ForkJoinPool.commonPool-worker-");
}
发现config也是作位运算,即config也会大于0,我们拿到这个config返回开始 par 赋值那一块。
private ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
int mode,
String workerNamePrefix) {
this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
this.config = (parallelism & SMASK) | mode; // SMASK等于65535,mode等于0
long np = (long)(-parallelism); // offset ctl counts
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
总结就是并行度 parallelism(大于0) 与 65535(111111…) 做两次与运算,即本身。继续回到之前
...
static final ForkJoinPool common;
static {
...
common = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ForkJoinPool>() {
public ForkJoinPool run() {
return makeCommonPool(); //common的config由此方法返回
}});
int par = common.config & SMASK; // report 1 even if threads disabled
commonParallelism = par > 0 ? par : 1;
}
这里判断,
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
public static int getCommonPoolParallelism() {
return commonParallelism;
}
在ThreadPerTaskExecutor 中 execute,他会为每个任务新开一个线程,而不是采用ForkJoinPool中的线程。
static final class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) { new Thread(r).start(); }
}
结论:jvm启动参数中 java.util.concurrent.ForkJoinPool.common 的值为 1
或者服务器核心数小于等于2,都会导致不采用ForkJoinPool 中的线程,而是新起一个线程。
如果服务器只有两核,假如业务需要:现在起了20个completablefuture任务,若使用默认线程池,那么就会创建20个线程,20个线程并发执行在两个cpu上竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上。
,