Using CycleBarrier to compute the vector sum in parallel
Imagine you have a large dataset divided into segments, and you want to process each segment in parallel. After processing each segment, you might need to combine the results before proceeding.
CyclicBarrier is useful for synchronizing all threads processing the segments, ensuring that all have finished processing before combining the results.
The DataProcessingExample class provided below performs a simple operation: it divides an array of integers into segments, computes the sum of each segment in separate threads, and then combines these sums to get the total sum.
- DataProcessingExample.java
import java.util.concurrent.CyclicBarrier; import java.util.concurrent.BrokenBarrierException; public class DataProcessingExample { private static final int NUM_PARTS = 4; private static int[] data = new int[1000]; // A large data collection private static int[] results = new int[NUM_PARTS]; // Partial results // Task for processing a part of the data static class DataProcessor implements Runnable { private int part; private CyclicBarrier barrier; DataProcessor(int part, CyclicBarrier barrier) { this.part = part; this.barrier = barrier; } @Override public void run() { // Processing a subset of the data System.out.println("Processing part: " + part); int sum = 0; for (int i = part * 250; i < (part + 1) * 250; i++) { sum += data[i]; // Simple processing (e.g., summing values) } results[part] = sum; try { // Wait for other threads to reach this point barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } } public static void main(String[] args) { // Initializing data for (int i = 0; i < data.length; i++) { data[i] = i; } CyclicBarrier barrier = new CyclicBarrier(NUM_PARTS, () -> { // Combining results after all threads have processed their part int totalSum = 0; for (int sum : results) { totalSum += sum; } System.out.println("Total sum: " + totalSum); }); for (int i = 0; i < NUM_PARTS; i++) { new Thread(new DataProcessor(i, barrier)).start(); } } }
Parallel Data Processing: The DataProcessor class is a task that processes a part of the data. In this example, it simply calculates the sum of a subset of the data array.
CyclicBarrier Usage: A CyclicBarrier is created with a number of parties equal to the number of processing tasks. An action is specified to run after all threads reach the barrier, which combines the results of each thread's processing.
Combining Results: The combining action calculates the total sum of all partial results and prints it out. This action is run once all threads have completed their processing tasks and have reached the barrier.
Main Method: Initializes the data and starts the threads, each processing a different part of the data. After processing, each thread waits at the barrier.
This example demonstrates a practical use of CyclicBarrier in a parallel computation context. It showcases how threads can work on separate parts of a large dataset and then synchronize to combine their results. This pattern is particularly useful in scenarios involving parallel computations like data analysis, image processing, or any scenario where a large task can be divided into smaller, independent subtasks.
The use of CyclicBarrier ensures that the final combining action (in this case, summing the results) does not occur until all processing threads have completed their work, thereby maintaining data integrity and correctness of the final result.