Over a million developers have joined DZone.

JDK9's ForkJoinPool Upgrades

DZone's Guide to

JDK9's ForkJoinPool Upgrades

Java 9 snuck in some small but important changes to the ForkJoinPool. Let's take a closer look.

· Java Zone ·
Free Resource

Verify, standardize, and correct the Big 4 + more– name, email, phone and global addresses – try our Data Quality APIs now at Melissa Developer Portal!

While everyone's been busy with modularity, local-variable-type-inference, and other "Next Big Things" from recent JDK releases, there's a fairly small but important update for the ForkJoinPool that deserves some attention.

ForkJoinPool was an experiment brought to life by JDK 7 that attracted a lot of attention. Its main selling point was the implementation of the idea of work-stealing. Simply put, free threads were able to steal tasks from worker queues of other busy threads within the same pool.

ForkJoinPool Configuration

Since the beginning, ForkJoinPool  suffered from a lack of reasonable config options. The most generous constructor offered us only parameters such as:

  1. Parallelism level
  2. A custom ForkJoinWorkerThreadFactory
  3. A custom UncaughtExceptionHandler
public ForkJoinPool(
int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode)

For some of you, that was a bit more intrusive. You would discover that there's one more private constructor available since JDK 8, which offers an additional, very useful parameter — the worker name prefix.

I must admit that it was very disappointing to see this one being private and not accessible using any legal means, but, luckily, there are other ways for achieving the same result.


JDK9 brought huge improvements Firstly, the implementation was rewritten using VarHandles, and we got a new, very generous constructor exposing additional configuration parameters such as:

public ForkJoinPool(
// ...
int corePoolSize,
int maximumPoolSize,
int minimumRunnable,
Predicate<? super ForkJoinPool> saturate,
long keepAliveTime, TimeUnit unit

Let's see what do those give us.

int corePoolSize

This one is pretty self-explanatory; the int corePoolSize  is the number of threads to keep in the pool. Normally (and * by default), this is the same value as the parallelism level, * but may be set to a larger value to reduce dynamic overhead if * tasks regularly block. Using a smaller value (for example, 0) has the same effect as the default. However, it'd be important to add that the maximum possible value is 32767.

int maximumPoolSize

The int maximumPoolSize is pretty self-explanatory, as well. By default, 256 spare threads are allowed.

int minimumRunnable

This is the first huge improvement that gives us an opportunity to ensure that there's at least N usable threads in the pool. Usable threads are those that aren't blocked by a  join()  or a  ManagedBlocker instance. When a number of free unblocked threads go below the provided value, new threads get spawned if the  maximumPoolSize  allows it.

Setting the  minimumRunnable to a larger value might ensure better throughput in the presence of blocking tasks for the cost of the increased overhead (remember to make sure that gains are bigger than costs).

If we know that our tasks won't need any additional threads, we can go for 0.

Predicate<? super ForkJoinPool> saturate

If we end up in a situation when there's an attempt made to spawn more threads in order to satisfy the  minimumRunnable  constraint, but it gets blocked by the  maximumPoolSize , by default,  RejectedExecutionException("Thread limit exceeded replacing blocked worker")  is thrown.

But now, we can provide a  Predicate that gets fired once such situation occurs and, eventually, allow thread pool saturation by ignoring the  minimumRunnablevalue.

It's good to see that we have a choice now.

long keepAliveTime, TimeUnit unit

Just like with the classicExecutorService, we can now specify how long unused threads should be kept alive before getting terminated.

Keep in mind that it applies only for threads spawned above the  corePoolSize  value.


JDK9 brought huge improvements forForkJoinPool.

Unfortunately, we still can't provide a custom worker name prefix easily and cap the size of the worker queue, which is now capped at "1 << 24." This is way too much for any reasonable value.

If you're interested in seeing the raw diff, you can find it here.

Developers! Quickly and easily gain access to the tools and information you need! Explore, test and combine our data quality APIs at Melissa Developer Portal – home to tools that save time and boost revenue. Our APIs verify, standardize, and correct the Big 4 + more – name, email, phone and global addresses – to ensure accurate delivery, prevent blacklisting and identify risks in real-time.

java ,jdk 9 ,fork join ,pool ,threads ,performance ,queues

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}