Мы запускаем приложение Akka на Java в среде с двумя процессорами, и мы наблюдали, что с каждым tell
новый поток запускается вместо того, чтобы извлекать их из пула. Диспетчер Akka по умолчанию использует ForkJoinPool
в качестве исполнителя по умолчанию. В коде CompletableFuture
есть следующая логика:
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
static final class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) { new Thread(r).start(); }
}
Значение commonPoolParallelism
взято из Runtime.getRuntime().availableProcessors() - 1
(если свойство JVM не указано) - ForkJoinPool.makeCommonPool()
.
Итак, в нашем случае, когда у нас всего два процессора, ForkJoinPool
вообще не используется. Вместо этого по умолчанию используется ThreadPerTaskExecutor
. Я знаю, что мы можем переопределить это (мы делаем, и это вызывает значительное увеличение производительности). Но это заставляет меня задуматься, почему это так? Почему лучше начинать новый поток каждый раз, чем повторно использовать один из пула даже для одного процессора (не говоря уже о двух)? Почему это поведение по умолчанию ForkJoinPool
?