CountDownLatch and CyclicBarrier in Java

Concurrency is an integral part of modern software development, especially in applications that require parallel processing and efficient utilization of system resources. Java, being a widely used programming language, provides several built-in mechanisms to support concurrent programming. Two of the most commonly used synchronization mechanisms in Java are CountDownLatch and CyclicBarrier. In this article, we will explore the concepts and usage of these two synchronization constructs.

CountDownLatch

CountDownLatch is a synchronization primitive that allows one or more threads to wait until a set of operations being performed in other threads completes. It works by creating a latch with an initial count, and each thread that needs to wait for the completion of these operations calls the await() method on the latch. The await() method blocks the calling thread until the count reaches zero.

Usage Example

Let's consider an example where we have a program that needs to gather information from multiple external APIs before proceeding to the next step. We can use CountDownLatch to ensure that the program waits until all API calls complete and data is gathered before proceeding. Here's how we can achieve this:

import java.util.concurrent.CountDownLatch;

public class DataGatherer {
    public static void main(String[] args) throws InterruptedException {
        int numberOfAPIs = 3;
        CountDownLatch latch = new CountDownLatch(numberOfAPIs);

        // Start threads to make API calls
        for (int i = 0; i < numberOfAPIs; i++) {
            Thread thread = new Thread(new APIWorker(latch));
            thread.start();
        }

        // Wait until all API calls complete
        latch.await();

        // All API calls completed, proceed to next step
        System.out.println("Data gathered from all APIs. Proceeding to the next step.");
    }
}

class APIWorker implements Runnable {
    private final CountDownLatch latch;

    public APIWorker(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        // Perform API call and gather data

        // Signal to the latch that the operation is complete
        latch.countDown();
    }
}

In this example, we create a CountDownLatch with an initial count of 3, indicating that we have 3 API calls to make before proceeding. Each thread created by the DataGatherer class would pass the same latch instance to the APIWorker class, which performs the API call and signals the latch by calling countDown() method. The DataGatherer class then waits for the latch count to reach zero using the await() method. Once all API calls complete, the program can proceed to the next step.

CyclicBarrier

CyclicBarrier is another synchronization construct that allows a set of threads to wait for each other at a common point before proceeding. Unlike CountDownLatch, CyclicBarrier allows the waiting threads to perform certain actions before releasing them all at once. This construct is particularly useful in situations where parallel tasks need to coordinate and synchronize.

Usage Example

Let's consider an example where we have a scenario of a multiplayer game where all players need to gather at a specific point on the map before starting the game. We can use CyclicBarrier to ensure that all players arrive at the designated location before the game begins. Here's how we can implement this using CyclicBarrier:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class MultiplayerGame {
    public static void main(String[] args) {
        int numberOfPlayers = 4;
        CyclicBarrier barrier = new CyclicBarrier(numberOfPlayers, () ->
                System.out.println("All players gathered. Starting the game now!")
        );

        // Start threads for each player
        for (int i = 0; i < numberOfPlayers; i++) {
            Thread playerThread = new Thread(new Player(barrier));
            playerThread.start();
        }
    }
}

class Player implements Runnable {
    private final CyclicBarrier barrier;

    public Player(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        // Perform player-specific actions

        try {
            // Signal arrival at the gathering point
            barrier.await();

            // Perform game actions after all players have gathered
            System.out.println("Player performing game actions.");

        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

In this example, we create a CyclicBarrier with the number of players and a Runnable action to be executed when all players reach the gathering point. Each player, represented by a separate thread, would pass the same barrier instance to the Player class. The Player class performs player-specific actions and then calls the await() method on the barrier to signal its arrival at the gathering point. Once all players have arrived, the Runnable action provided to the barrier is executed. Finally, each player proceeds to perform game actions.

Conclusion

CountDownLatch and CyclicBarrier are powerful synchronization constructs provided by Java that can simplify the coordination and synchronization of multiple threads. They allow us to control the flow of execution and ensure that certain operations happen in a coordinated manner. Understanding and effectively using these constructs can significantly enhance the performance and reliability of concurrent applications in Java.


noob to master © copyleft