Coordinating threads with CountDownLatch

The CountDownLatch class allows us to coordinate the starting and stopping of threads. Typical uses are as follows:

Introduction to CountDownLatch

In concurrent programming, a latch is a type of "switch" or "trigger". The latch is set up with a particular count value. The count is then counted down, and at strategic moments, a thread or threads waits for the countdown to reach zero before continuing to perform some process. Note that this is a one-off process: once the latch reaches zero, there is no way to reset the count.

In Java:

The CountDownLatch class is designed to be safe to call from multiple threads without any extra synchronization. (This differs, for example, from the wait/notify mechanism, where threads must be synchronized on the given lock object before calling wait() or notify().)

Why use CountDownLatch (rather than wait/notify, Condition etc)?

The CountDownLatch protects you against the case of a thread missing a signal which can occur if you use these other mechanisms for coordinating jobs. Something like a Condition is useful for signalling to threads if they are waiting, but where it doesn't matter if they're not, or where a thread will explicitly check if it has to wait before waiting. With a CountDownLatch, we await a signal if it hasn't been triggered yet, but immediately continue without waiting if that signal was already triggered before we start waiting.

How to make several threads start at the same time

Sometimes it is useful to make a group of threads start at approximately the same time. For example, consider performance tests such as the ones conducted for this web site. If we're testing some throughput with n threads, it's only fair if the n threads start (and stop) at more or less the same time. In another situation, we might want a group of threads to start as soon as some asynchronous initialisation procedure is complete.

To coordinate the starting of several threads, we first create a CountDownLatch with an initial count of 1. Then, each thread will sit at the start of its run() method, waiting for the latch to be counted down (i.e. sitting in the await() method). The thread performing the initialisation step (or just the thread coordinating the start of the other threads in the case of our performance experiment) then calls countDown() on the latch. Because the initial count was 1, this single countdown operation triggers all the other threads to start at (approximately) the same time.

If we define a subclass of Thread to handle the concurrent tasks, then we can arrange to pass the CountDownLatch into the constructor of those threads:

public class LatchedThread extends Thread {
  private final CountDownLatch startLatch;

  public LatchedThread(CountDownLatch startLatch) {
    this.startLatch = startLatch;
  }
  public void run() {
    try {
      startLatch.await();
      // ... perform task
    } catch (InterruptedException iex) {}
  }
}

Then, to coordinate the starting of 4 of these threads:

CountDownLatch startLatch = new CountDownLatch(1);
for (int threadNo = 0; threadNo < 4; threadNo++) {
  Thread t = new LatchedThread(startLatch);
  t.start();
}
// give the threads chance to start up; we could perform
// initialisation code here as well.
Thread.sleep(200);
startLatch.countDown();

When we call countDown() in the main thread, we don't actually know that all of the threads have started up; we just assume that sleeping for a fraction of a second gives them a "reasonable chance" of being ready for the signal. If any of the threads "misses the signal", it won't actually matter too much: when such a thread does start up, enter its run() method await() method, it will no longer actually wait, since the latch has already reached zero.

Bear in mind that inevitably, threads will "wake up" with approximate simultaneity: how simultaneous it can actually be depends on various factors, such as whether each thread can actually be allocated to a free processor, how "busy" the system is (what other threads are running and at what priorities), what threads are doing— i.e. how quickly running threads will relinquish the CPU— and what your particular operating system's policy is on prioritising waiting threads when they are signalled to wake up. (See the section on thread scheduling for more details about these factors.)

How to wait for several threads to complete

Another common scenario is with parallel processing, where we need to wait for several threads to finish or reach a particular point. In this case, we can use a similar mechanism:

This is therefore more flexible than the join() method, which only lets us wait for a single thread. Here is an example of waiting for 10 threads to complete:

public class StopLatchedThread extends Thread {
  private final CountDownLatch stopLatch;
  
  public StopLatchedThread(CountDownLatch stopLatch) {
    this.stopLatch = stopLatch;
  }
  public void run() {
    try {
      // perform interesting task
    } finally {
      stopLatch.countDown();
    }
  }
}

public void performParallelTask() throws InterruptedException {
  CountDownLatch cdl = new CountDownLatch(10);
  for (int i = 0; i < 10; i++) {
    Thread t = new StopLatchedThread(cdl);
    t.start();
  }
  cdl.await();
}

Interruptions and timeouts

A thread sitting in the await() method can be interrupted (generally by another thread calling interrupt() on it). Therefore, the await() method throws InterruptedException. Inside a run() method, the most appropriate action is usually to catch the exception around the whole logic of the method, so that interrupting the thread makes it exit. Where we are waiting for threads to complete inside a method, we can just make that method throw the exception up, and let the caller worry about what happens if the process is interrupted. For more information, see the section on thread interruption.

A version of the await() method takes a timeout (and TimeUnit in which the timeout is specified). Setting a timeout could be useful if, for example, the condition that a thread is awaiting is the initialisation of a driver, and there's a chance that the driver will not get initialised in a reasonably amount of time. In the timed case, the method returns true if the latch was actually triggered, and false if a timeout occurred. The timed method can still be interrupted and throw InterruptedException.

Coordinating multi-stage/iterated parallel processes

The CountDownLatch is useful for coordination of one-off operations. In the next section, we look at the CyclicBarrier class, which allows repeated or multi-stage parallel processes to be coordinated.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.