Why to use CountDownLatch in Java MultiThreading ?

CountDownLatch is a synchronization aid that allows one or more threads to wait until a set of operations being performed by other threads is completed. It is particularly useful when you have a scenario where multiple threads need to complete certain tasks before another thread proceeds.

Key Use Cases for CountDownLatch:

  1. Waiting for multiple threads to complete: A common use case is when the main thread or another coordinating thread must wait for several worker threads to finish their tasks before proceeding.
  2. Starting multiple threads at the same time: CountDownLatch can be used to ensure that multiple threads start simultaneously after some preparation work is done.
  3. Dividing tasks: You can divide a task into multiple parts, and after all parts are processed by different threads, a final task can be executed.

How CountDownLatch Works:

  • CountDownLatch(int count): The latch is initialized with a count. This count is decremented by one every time a worker thread calls countDown().
  • await(): A thread calling await() will be blocked until the count reaches zero (i.e., all worker threads have completed their work).
  • countDown(): Worker threads call this method when they finish their work, reducing the count by 1.

Example 1: Waiting for Multiple Threads to Complete

In this example, the main thread waits for three worker threads to finish their tasks.

import java.util.concurrent.CountDownLatch;

class Worker extends Thread {
    private final CountDownLatch latch;

    public Worker(String name, CountDownLatch latch) {
        super(name);
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is doing some work...");
        try {
            Thread.sleep(2000); // Simulate work by sleeping
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " has finished.");

        // Decrease the latch count by 1
        latch.countDown();
    }
}

public class Main {
    public static void main(String[] args) {
        // Create a CountDownLatch with a count of 3 (because we have 3 workers)
        CountDownLatch latch = new CountDownLatch(3);

        // Start three worker threads
        Worker worker1 = new Worker("Worker-1", latch);
        Worker worker2 = new Worker("Worker-2", latch);
        Worker worker3 = new Worker("Worker-3", latch);

        worker1.start();
        worker2.start();
        worker3.start();

        // The main thread will wait until the latch count reaches 0
        System.out.println("Main thread is waiting for workers to complete...");
        try {
            latch.await(); // Block until the count reaches 0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("All workers have finished. Main thread resumes.");
    }
}

Explanation:

  1. CountDownLatch(3): The latch is initialized with a count of 3. This means the main thread will wait until 3 worker threads call countDown().
  2. latch.await(): The main thread will wait here until the count reaches zero.
  3. latch.countDown(): Each worker thread calls countDown() after completing its work, reducing the latch count.
  4. Once all worker threads have called countDown(), the latch count becomes 0, and the main thread resumes.

Output:

Main thread is waiting for workers to complete...
Worker-1 is doing some work...
Worker-2 is doing some work...
Worker-3 is doing some work...
Worker-1 has finished.
Worker-2 has finished.
Worker-3 has finished.
All workers have finished. Main thread resumes.

Example 2: Ensuring Multiple Threads Start Simultaneously

You can use CountDownLatch to make sure that several threads start working at the same time after they are all ready.

import java.util.concurrent.CountDownLatch;

class WorkerTask extends Thread {
    private final CountDownLatch startSignal;

    public WorkerTask(String name, CountDownLatch startSignal) {
        super(name);
        this.startSignal = startSignal;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " is ready to start...");
            startSignal.await();  // Wait until the main thread triggers the latch
            System.out.println(Thread.currentThread().getName() + " is working...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Create a CountDownLatch with a count of 1 (to release all threads at once)
        CountDownLatch startSignal = new CountDownLatch(1);

        // Create and start worker threads
        WorkerTask worker1 = new WorkerTask("Worker-1", startSignal);
        WorkerTask worker2 = new WorkerTask("Worker-2", startSignal);
        WorkerTask worker3 = new WorkerTask("Worker-3", startSignal);

        worker1.start();
        worker2.start();
        worker3.start();

        // Main thread does some work, then releases all workers at the same time
        System.out.println("Main thread preparing to start workers...");
        Thread.sleep(2000); // Simulate preparation time

        System.out.println("Main thread signals all workers to start.");
        startSignal.countDown(); // Release all workers at the same time
    }
}

Explanation:

  1. CountDownLatch(1): The latch starts with a count of 1, which is used to signal all worker threads simultaneously.
  2. startSignal.await(): Each worker thread waits for the main thread to signal (reduce the latch count to 0).
  3. startSignal.countDown(): The main thread calls this to release all worker threads at once, after its preparation is done.

Output:

Worker-1 is ready to start...
Worker-2 is ready to start...
Worker-3 is ready to start...
Main thread preparing to start workers...
Main thread signals all workers to start.
Worker-1 is working...
Worker-2 is working...
Worker-3 is working...

Summary of CountDownLatch Usage:

  • Use CountDownLatch when you need one or more threads to wait until a set of operations in other threads completes.
  • It allows coordination between threads, ensuring proper synchronization where certain tasks cannot proceed until others are finished.
  • Common scenarios include:
  • Waiting for multiple threads to complete before proceeding.
  • Starting multiple threads simultaneously after an initial setup.

In both examples, CountDownLatch ensures that threads either start simultaneously or wait for others to finish before continuing, making it a useful tool in multithreading scenarios where timing and synchronization are important.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *