JDK9's ForkJoinPool Upgrades
JDK9's ForkJoinPool Upgrades
Java 9 snuck in some small but important changes to the ForkJoinPool. Let's take a closer look.
Join the DZone community and get the full member experience.Join For Free
"I love writing authentication and authorization code." ~ No Developer Ever. Try Okta Instead.
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.
Since the beginning,
ForkJoinPool suffered from a lack of reasonable config options. The most generous constructor offered us only parameters such as:
- Parallelism level
- A custom ForkJoinWorkerThreadFactory
- 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.
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.
The int maximumPoolSize is pretty self-explanatory, as well. By default, 256 spare threads are allowed.
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.
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
It's good to see that we have a choice now.
long keepAliveTime, TimeUnit unit
Just like with the classic
ExecutorService, 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
JDK9 brought huge improvements for
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.
Published at DZone with permission of Grzegorz Piwowarek , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.