How to configure a single-threaded ForkJoinPool?
So, it turns out I was wrong.
When you configure a ForkJoinPool
with parallelism
set to 1, only one thread executes the tasks. The main
thread is blocked on ForkJoin.get()
. It doesn't actually execute any tasks.
That said, it turns out that it is really tricky providing deterministic behavior. Here are some of the problems I had to correct:
ForkJoinPool
was executing tasks using different worker threads (with different names) if the worker thread became idle long enough. For example, if the main thread got suspended on a debugging breakpoint, the worker thread would become idle and shut down. When I would resume execution,ForkJoinThread
would spin up a new worker thread with a different name. To solve this, I had to provide a customForkJoinWorkerThreadFactory
implementation that returnsnull
if theForkJoinPool
already has a live worker (this prevents the pool from creating multiple workers). I also had ensure that my code was returning the sameRandom
instance even if a worker thread shut down and came back again.- Collections with non-deterministic iteration order such as
HashMap
orHashSet
led to elements grabbing random numbers in a different order on every run. I corrected this by usingLinkedHashMap
andLinkedHashSet
. - Objects with non-deterministic hashCode() implementations, such as
Enum.hashCode()
. I forget what problems this caused but I corrected it by calculating the hashCode() myself instead of relying on the built-in method.
Here is a sample implementation of ForkJoinWorkerThreadFactory:
class MyForkJoinWorkerThread extends ForkJoinWorkerThread{ MyForkJoinWorkerThread(ForkJoinPool pool) { super(pool); // Change thread name after ForkJoinPool.registerWorker() does the same setName("DETERMINISTIC_WORKER"); }}ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory(){ private WeakReference<Thread> currentWorker = new WeakReference<>(null); @Override public synchronized ForkJoinWorkerThread newThread(ForkJoinPool pool) { // If the pool already has a live thread, wait for it to shut down. Thread thread = currentWorker.get(); if (thread != null && thread.isAlive()) { try { thread.join(); } catch (InterruptedException e) { log.error("", e); } } ForkJoinWorkerThread result = new MyForkJoinWorkerThread(pool); currentWorker = new WeakReference<>(result); return result; }};
Main thread is always the first thread your application will create. So when you create a ForkJoinPool
with parallelism
of 1
, you are creating another thread. Effectively there will be two threads in the application now ( because you created a pool
of threads ).
If you need only one thread that is Main, you can execute your code in sequence ( and not in parallel at all ).