Foo
Foo

Reputation: 4606

Java CompletableFuture threadpool used

In the code below no matter what I set as the max value of i, the total number of threads never crosses 13. What thread pool does it use? Where can I find its default settings?

public static void main(String[] args) {
    // write your code here
    for (int i = 0; i <= 5; i++) {

        System.out.println("kick off" + i);
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(1000);

                System.out.println(java.lang.Thread.activeCount());
            }
            catch (Exception e) {
                System.out.println("error");
            }
        });

    }
    System.out.println(java.lang.Thread.activeCount());
    try {
        Thread.sleep(10000);
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Upvotes: 9

Views: 15874

Answers (1)

Zabuzard
Zabuzard

Reputation: 25903

Answer

It is determined either by your system settings or based on your current amount of processors.


Documentation & Code

From the documentation of CompletableFuture:

All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool() (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task). This may be overridden for non-static methods in subclasses by defining method defaultExecutor(). [...]

From the documentation of ForkJoinPool#commonPool():

Returns the common pool instance. This pool is statically constructed; [...]

From the documentation of the class ForkJoinPool itself:

The parameters used to construct the common pool may be controlled by setting the following system properties:

  • java.util.concurrent.ForkJoinPool.common.parallelism - the parallelism level, a non-negative integer
  • java.util.concurrent.ForkJoinPool.common.threadFactory - the class name of a ForkJoinPool.ForkJoinWorkerThreadFactory. The system class loader is used to load this class.
  • java.util.concurrent.ForkJoinPool.common.exceptionHandler - the class name of a Thread.UncaughtExceptionHandler. The system class loader is used to load this class.
  • java.util.concurrent.ForkJoinPool.common.maximumSpares - the maximum number of allowed extra threads to maintain target parallelism (default 256).

If no thread factory is supplied via a system property, then the common pool uses a factory that uses the system class loader as the thread context class loader.

From there on, we have to check the actual source code. From ForkJoinPool.java#L3208:

common = AccessController.doPrivileged(new PrivilegedAction<>() {
    public ForkJoinPool run() {
        return new ForkJoinPool((byte)0); }});

From the constructor at ForkJoinPool.java#L2345:

// [...]
String pp = System.getProperty("java.util.concurrent.ForkJoinPool.common.parallelism");
// [...]
if (pp != null)
    parallelism = Integer.parseInt(pp);
// [...]
if (parallelism < 0 && // default 1 less than #cores
    (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
        parallelism = 1;
// [...]
int n = (parallelism > 1) ? parallelism - 1 : 1;
n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16;
n = (n + 1) << 1;
// [...]
this.workQueues = new WorkQueue[n];

And there you go. It is determined either by your system settings or based on your current amount of processors.


Example math

Supposed you did not set anything for java.util.concurrent.ForkJoinPool.common.parallelism, lets quickly do the math for the code:

parallelism starts with -1 in that case, so we have

if (parallelism < 0 && // default 1 less than #cores
    (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
        parallelism = 1;

Let us suppose you have 8 cores on your machine. So you execute parallelism = Runtime.getRuntime().availableProcessors() - 1 which assigns 7 to parallelism. You do not enter the if, so we continue.

Next up we have

int n = (parallelism > 1) ? parallelism - 1 : 1;

Which subtracts one of it, so n = 6.

Then

n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16;
n = (n + 1) << 1;

Results in 16.

What this does is basically subtracting 2 and then going to the next power of 2 and then doubling it.

So if you have 6 cores, it goes to 4, then to 8 and then times 2, so 16.


So why 13?

So why do you get 13? I suppose you have 4 or 5 processors, so the pool would use 8 threads. However, you are measuring the total amount of threads used by Java in total.

System.out.println(java.lang.Thread.activeCount());

And Java probably uses around 13 - 8 = 5 threads for other "standby" things right now.

Upvotes: 18

Related Questions