Java.util.concurrent.RecursiveAction class in Java with Examples
The java.util.concurrent.RecursiveAction class is a part of the Java Concurrency API that provides support for dividing tasks into smaller subtasks and executing them in a parallel manner. It is designed for tasks that do not return a result but rather perform some action or computation.
The RecursiveAction class provides a powerful abstraction for parallel computation and is useful when dealing with tasks that can be divided into smaller, independent units. By utilizing the fork-join framework and the methods provided by RecursiveAction, developers can efficiently parallelize their computations and take advantage of available processing resources.
Key Aspects of Recursive Action
- Inheritance: RecursiveAction extends the abstract class ForkJoinTask, which provides the basic functionality for managing tasks in the fork-join framework.
- Abstract Method: RecursiveAction is an abstract class and contains an abstract method called compute(). This method must be overridden by subclasses to define the task's logic.
- Dividing Tasks: The primary purpose of RecursiveAction is to facilitate the division of tasks into smaller subtasks. This division is typically done in the compute() method of the subclass.
- Fork-Join Framework: The RecursiveAction tasks are typically executed using the fork-join framework. This framework leverages the available processor cores to execute the subtasks in parallel.
- Parallel Execution: By dividing the tasks into smaller subtasks, the fork-join framework enables parallel execution. The subtasks are executed concurrently, and their results are combined as needed.
- Forking and Joining: The RecursiveAction class provides methods for forking and joining subtasks. The fork() method is used to schedule a subtask for asynchronous execution, and the join() method is used to wait for the completion of a subtask.
- Thresholds: In many cases, RecursiveAction tasks have a threshold value that determines when a task becomes small enough to be executed directly instead of being further divided. This threshold helps balance the overhead of task division and the benefits of parallel execution.
Example 1
In the above code, the Main class extends RecursiveAction and represents the task that will be executed in parallel. The compute() method is where the task's logic resides. It checks if the range of values is small enough to perform the computation directly or if it needs to be divided into smaller subtasks.
FileName: MyRecursive.java
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; public class MyRecursive extends RecursiveAction { private static final int THRESHOLD = 10; private int start; private int end; public Main(int start, int end) { this.start = start; this.end = end; } @Override protected void compute() { if (end - start <= THRESHOLD) { // Perform the computation for a small range of values for (int i = start; i <= end; i++) { // Do some computation or modify the state System.out.println("Processing: " + i); } } else { // Divide the task into smaller subtasks int mid = (start + end) / 2; Main left = new Main(start, mid); Main right = new Main(mid + 1, end); // Execute the subtasks in parallel invokeAll(left, right); } } public static void main(String[] args) { ForkJoinPool forkJoinPool = new ForkJoinPool(); Main main = new Main(1, 15); forkJoinPool.invoke(main); } }
Output
Processing: 1 Processing: 2 Processing: 3 Processing: 4 Processing: 5 Processing: 6 Processing: 7 Processing: 8 Processing: 9 Processing: 10 Processing: 11 Processing: 12 Processing: 13 Processing: 14 Processing: 15
Example 2
In the above code, we have a ParallelSum class that extends RecursiveAction and calculates the sum of an array of integers. The compute() method is responsible for performing the sum calculation. In the main() method, we create an array of integers and populate it with values from 1 to 10000. Then, a ForkJoinPool is created, and an instance of ParallelSum is created to calculate the sum of the array. Finally, the task is invoked using invoke() method, and the final sum is printed.
FileName: ParallelSum.java
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; public class ParallelSum extends RecursiveAction { private static final int THRESHOLD = 1000; private int[] array; private int start; private int end; private int result; public ParallelSum(int[] array, int start, int end) { this.array = array; this.start = start; this.end = end; } @Override protected void compute() { if (end - start <= THRESHOLD) { // Perform the sum for a small range of values for (int i = start; i <= end; i++) { result += array[i]; } } else { // Divide the task into smaller subtasks int mid = (start + end) / 2; ParallelSum left = new ParallelSum(array, start, mid); ParallelSum right = new ParallelSum(array, mid + 1, end); // Execute the subtasks in parallel invokeAll(left, right); // Combine the results of the subtasks result = left.result + right.result; } } public static void main(String[] args) { int[] array = new int[10000]; for (int i = 0; i < array.length; i++) { array[i] = i + 1; } ForkJoinPool forkJoinPool = new ForkJoinPool(); ParallelSum parallelSum = new ParallelSum(array, 0, array.length - 1); forkJoinPool.invoke(parallelSum); System.out.println("Sum: " + parallelSum.result); } }
Output
Sum: 50005000