Coordinating threads with CountDownLatch
The CountDownLatch class allows us to coordinate the starting and
stopping of threads. Typical uses are as follows:
- we can make several threads start at the same time;
- we can wait for several threads to finish (whereas, for
example, the Thread.join() method
only lets you wait for a single thread).
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 object is constructed with the initial count;
- calling countDown() decrements the count by 1;
- the await() method will wait for the count to reach zero,
or proceed immediately if the count already reached zero.
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:
- we construct a CountDownLatch with the number of threads we
want to wait for;
- each thread counts down the latch on termination (or on finishing
the job we're interested in).
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.