Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is ForkJoinPool Async mode

Tags:

What does Async mode of ForkJoinPool mean? Javadoc mentions that it makes queues (is it per-thread queue?) FIFO instead of LIFO. What does it mean in practice?

like image 721
Primk Avatar asked Apr 12 '11 18:04

Primk


People also ask

What is ForkJoinPool how it works?

ForkJoinPool It is an implementation of the ExecutorService that manages worker threads and provides us with tools to get information about the thread pool state and performance. Worker threads can execute only one task at a time, but the ForkJoinPool doesn't create a separate thread for every single subtask.

What is difference between ExecutorService and ForkJoinPool?

The Fork/Join framework in Java 7 is an implementation of the Divide and Conquer algorithm, in which a central ForkJoinPool executes branching ForkJoinTasks. ExecutorService is an Executor that provides methods to manage the progress-tracking and termination of asynchronous tasks.

What is ForkJoinPool parallelism?

A ForkJoinPool is constructed with a given target parallelism level; by default, equal to the number of available processors. The pool attempts to maintain enough active (or available) threads by dynamically adding, suspending, or resuming internal worker threads, even if some tasks are stalled waiting to join others.

How many threads are created in ForkJoinPool?

Implementation notes: This implementation restricts the maximum number of running threads to 32767. Attempts to create pools with greater than the maximum number result in IllegalArgumentException .


2 Answers

Each worker thread in a ForkJoinPool has its own work queue. Async mode concerns the order in which each worker takes forked tasks that are never joined from its work queue.

Workers in a ForkJoinPool in async mode process such tasks in FIFO (first in, first out) order. By default, ForkJoinPools process such tasks in LIFO (last in, first out) order.

It's important to emphasise that the async mode setting only concerns forked tasks that are never joined. When using a ForkJoinPool for what it was originally designed for, namely recursive fork/join task decomposition, asyncMode doesn't come into play at all. Only when a worker is not engaged in actual fork/join processing does it execute async tasks, and only then is the asyncMode flag actually queried.

Here's a small program that demonstrates the difference between the two different async mode settings:

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Demo of {@code ForkJoinPool} behaviour in async and non-async mode.
 */
public class ForkJoinAsyncMode {
    public static void main(String[] args) {
        // Set the asyncMode argument below to true or false as desired:
        ForkJoinPool pool = new ForkJoinPool(
                4, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);

        pool.invoke(new RecursiveRangeAction(0, 200));
        pool.awaitQuiescence(2L, TimeUnit.SECONDS);
    }

    /**
     * A {@code ForkJoinTask} that prints a range if the range is smaller than a
     * certain threshold; otherwise halves the range and proceeds recursively.
     * Every recursive invocation also forks off a task that is never joined.
     */
    private static class RecursiveRangeAction extends RecursiveAction {
        private static final AtomicInteger ASYNC_TASK_ID = new AtomicInteger();

        private final int start;
        private final int end;

        RecursiveRangeAction(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected void compute() {
            if (end - start < 10) {
                System.out.format("%s range [%d-%d] done%n",
                        Thread.currentThread().getName(), start, end);
            } else {
                int mid = (start + end) >>> 1;
                int id = ASYNC_TASK_ID.incrementAndGet();

                System.out.format(
                        "%1$s [%2$d-%3$d] -< [%3$d-%4$d], fork async task %5$d%n",
                        Thread.currentThread().getName(), start, mid, end, id);

                // Fork off additional asynchronous task that is never joined.
                ForkJoinTask.adapt(() -> {
                    System.out.format("%s async task %d done%n",
                            Thread.currentThread().getName(), id);
                }).fork();

                invokeAll(new RecursiveRangeAction(start, mid),
                        new RecursiveRangeAction(mid, end));
            }
        }
    }
}

In non-async mode (the default for ForkJoinPool), forked tasks that are never joined are executed in LIFO order.

When you run the example program in non-async mode, looking at the output of one worker you might see a pattern like the following:

ForkJoinPool-1-worker-0 [175-187] -< [187-200], fork async task 10
ForkJoinPool-1-worker-0 [175-181] -< [181-187], fork async task 11
ForkJoinPool-1-worker-0 range [175-181] done
ForkJoinPool-1-worker-0 range [181-187] done
ForkJoinPool-1-worker-0 [187-193] -< [193-200], fork async task 12
ForkJoinPool-1-worker-0 range [187-193] done
ForkJoinPool-1-worker-0 range [193-200] done
ForkJoinPool-1-worker-0 async task 12 done
ForkJoinPool-1-worker-0 async task 11 done
ForkJoinPool-1-worker-0 async task 10 done

Here, tasks 10, 11, 12 are forked and later executed in reverse order once the worker gets around to executing them.

In async mode on the other hand, again looking at the output of one worker the pattern would rather look like the following:

ForkJoinPool-1-worker-3 [150-175] -< [175-200], fork async task 8
ForkJoinPool-1-worker-3 [150-162] -< [162-175], fork async task 9
ForkJoinPool-1-worker-3 [150-156] -< [156-162], fork async task 10
ForkJoinPool-1-worker-3 range [150-156] done
ForkJoinPool-1-worker-3 range [156-162] done
ForkJoinPool-1-worker-3 [162-168] -< [168-175], fork async task 11
...
ForkJoinPool-1-worker-3 async task 8 done
ForkJoinPool-1-worker-3 async task 9 done
ForkJoinPool-1-worker-3 async task 10 done
ForkJoinPool-1-worker-3 async task 11 done

Tasks 8, 9, 10, 11 are forked and later executed in the order they were submitted.

When to use which mode? Whenever a ForkJoinPool thread pool is chosen to take advantage of its work-stealing properties rather than for recursive fork/join task processing, async mode is probably the more natural choice, as tasks get executed in the order they are submitted.

Async event-driven frameworks like CompletableFuture are sometimes said to profit from async mode. For example, when constructing a complex chain of CompletableFuture callbacks, then a custom ForkJoinPool executor in async mode might perform slightly better than the default executor. (I can't speak from experience though.)

like image 168
glts Avatar answered Sep 21 '22 16:09

glts


It is meant for event-style tasks that are submitted but never joined. So basically tasks that are getting executed for their side-effects, not for returning a result that will be processed by the forking task after joining.

like image 36
hotzen Avatar answered Sep 17 '22 16:09

hotzen