9

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();
    }
}
1
  • 2
    How many processors has your computer? (Runtime.getRuntime().availableProcessors())
    – 1453
    Commented Jul 16, 2020 at 7:41

1 Answer 1

18

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.

1
  • 2
    Damn, how fast you are. And I wanted to wait for the OP to tell his processor amount so I can mention it in my answer.
    – 1453
    Commented Jul 16, 2020 at 7:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.