RecursiveTask vs RecursiveAction — how do you write a for… — Cracked Java
// Concurrency & Multithreading · Fork/Join & Parallel Streams
SeniorTheoryCoding

RecursiveTask vs RecursiveAction — how do you write a fork/join task?

Both are the abstract task types you subclass for fork/join work: RecursiveTask<V> returns a result from compute(), while RecursiveAction returns nothing (void). Pick RecursiveTask when you compute and combine a value (a sum, a count, a merged list); pick RecursiveAction for in-place work with no return (sorting a sub-array, applying a transform to a slice).

The fork/compute/join idiom

The body of compute() follows one shape: a base case below a threshold, and a recursive case that splits, forks one half, computes the other inline, and joins.

class SumTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 10_000;
    private final long[] arr;
    private final int lo, hi;

    SumTask(long[] arr, int lo, int hi) { this.arr = arr; this.lo = lo; this.hi = hi; }

    @Override
    protected Long compute() {
        if (hi - lo <= THRESHOLD) {          // base case: solve directly
            long sum = 0;
            for (int i = lo; i < hi; i++) sum += arr[i];
            return sum;
        }
        int mid = (lo + hi) >>> 1;
        SumTask left  = new SumTask(arr, lo, mid);
        SumTask right = new SumTask(arr, mid, hi);
        left.fork();                          // schedule left asynchronously
        long rightResult = right.compute();   // compute right in THIS thread
        long leftResult  = left.join();       // wait for / help run left
        return leftResult + rightResult;
    }
}

long total = ForkJoinPool.commonPool().invoke(new SumTask(data, 0, data.length));

The rules that matter

  • Compute one side, fork the other. Calling left.fork(); right.fork(); left.join(); right.join(); wastes the current thread waiting. Computing one half inline keeps every worker productive.
  • join() order: forked-last, joined-first isn't required, but compute the un-forked half before joining the forked one — that's the work-stealing-friendly pattern.
  • Pick a real threshold. Too small and you drown in task-creation and scheduling overhead; too large and you under-utilize cores. Aim for enough tasks to balance across workers (a common heuristic is ~10× the parallelism level) while each task does meaningful work.
  • Never block on I/O or locks inside compute() — it ties up a scarce worker.

Mark your status