1. Overview
In this tutorial, we're going to examine the usage of CountDownLatch in Java. We can think of latch as a gate in that it is closed at first and no threads can pass. But when a set of operations on other threads complete, the gate opens and threads are allowed to pass. When the gate is open, the latch is at its terminal state and it remains open forever.
2. CountDownLatch Usage
A CountDownLatch is initialized with a given count. The countDown method reduces the current count by one. And the threads invoking the await method block until the current count reaches zero. For example, when we initialize a latch with a count of 10, all await methods block until countDown is called 10 times. When the count reaches zero, it can't be reset to its initial state, so subsequent await invocations return immediately without blocking.
Let's see an example where the worker threads wait for another thread to signal the start.
Firstly, we have the Worker class:
public class Worker implements Runnable {
private final CountDownLatch startSignal;
Worker(CountDownLatch startSignal) {
this.startSignal = startSignal;
}
public void run() {
try {
System.out.println("Ready to start.");
startSignal.await();
doWork();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
System.out.println("Interrupted.");
}
}
void doWork() {
System.out.println("Doing work.");
}
}
Here, the Worker class implements Runnable. It also expects a latch in the constructor - startSignal. Then in the run method, before doing some work, Worker is invoking the await() method of the latch. As a result, the execution blocks until the latch count reaches zero.
Now that we have our task, let's execute it:
public void countDownLatch() throws InterruptedException {
final int threadCount = 5;
final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
final CountDownLatch startSignal = new CountDownLatch(1);
for (int i = 0; i < threadCount; i++) {
threadPool.execute(new Worker(startSignal));
}
TimeUnit.SECONDS.sleep(1);
startSignal.countDown(); // Let all workers proceed
threadPool.shutdown();
}
In this example, we're initializing a CountDownLatch with a count of 1 - startSignal. Then we're executing five Worker tasks initialized with this latch. Remember that the Worker task waits on the CountDownLatch instance until its count reaches zero. Thus, for the tasks to make any progress, we're invoking the startSignal.countDown() method. After this invocation, the latch count becomes zero, and all five threads resume their execution.
A sample run shows the coordinated execution:
Ready to start.
Ready to start.
Ready to start.
Ready to start.
Ready to start.
Doing work.
Doing work.
Doing work.
Doing work.
Doing work.
3. Properties of CountDownLatch
Next, we'll examine some important properties of CountDownLatch.
3.1. Can't Reset the CountDownLatch
As mentioned previously, we can't reset a CountDownLatch. Once its count reaches zero, it remains that way. If we need to reset the latch, we can use a CyclicBarrier or Phaser instead.
3.2. Event-Driven Behavior
Another important property is that, in terms of manipulating its internal state, CountDownLatch is event-driven more than it is thread-driven. For example, a latch initialized to N doesn't need to wait for N threads to invoke countDown(). A single thread can also invoke countDown() N times. In the N-thread case, each thread generates a single event whereas, in the latter, all events are generated on the same thread:
public void countDownMultipleTimesOnTheSameThread() {
final CountDownLatch latch = new CountDownLatch(3);
latch.countDown();
System.out.println("Latch count: " + latch.getCount());
latch.countDown();
System.out.println("Latch count: " + latch.getCount());
latch.countDown();
System.out.println("Latch count: " + latch.getCount());
}
Here, we're invoking countDown() three times on the same thread.
A sample run prints:
Latch count: 2
Latch count: 1
Latch count: 0
4. Summary
In this tutorial, we've investigated how we can use CountDownLatch in Java. Primarily it is a synchronization tool that allows threads to coordinate their operations.
As always, the source code for all examples in this tutorial is available on Github.