1. Introduction
The CountDownLatch
class is a powerful concurrency construct provided by Java. Its purpose is to allow one or more threads to wait until a given set of operations performed by other threads completes. Essentially, it acts as a synchronization aid.
2. Basics of CountDownLatch
- Initialization:
- A
CountDownLatch
is initialized with a specific count (usually the number of threads we want to wait for). - This count represents the number of times the
countDown()
method must be called before the waiting threads are released.
- A
- Decrementing the Count:
- Threads that complete their work call the
countDown()
method. - Each call decrements the internal counter.
- When the counter reaches zero, the waiting threads are unblocked.
- Threads that complete their work call the
- Waiting Threads:
- Threads waiting for the counter to reach zero call one of the
await()
methods. - The calling thread blocks until the counter becomes zero or until it’s interrupted.
- Threads waiting for the counter to reach zero call one of the
3. Practical Examples
Example 1: Waiting for a Pool of Threads to Complete
Let’s create a scenario where we have multiple pizza makers (threads) preparing pizzas. We’ll use a CountDownLatch
to ensure that the main thread waits until all pizza makers finish making their pizzas.
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PizzaMakerDemo {
public static void main(String[] args) throws InterruptedException {
List<String> outputScraper = Collections.synchronizedList(new ArrayList<>());
int numPizzaMakers = 5; // Number of pizza makers
// Initialize the CountDownLatch with the number of pizza makers
CountDownLatch countDownLatch = new CountDownLatch(numPizzaMakers);
// Create pizza maker threads
List<Thread> pizzaMakers = Stream.generate(() -> new Thread(new PizzaMaker(outputScraper, countDownLatch)))
.limit(numPizzaMakers)
.collect(Collectors.toList());
// Start the pizza maker threads
pizzaMakers.forEach(Thread::start);
// Wait for all pizza makers to finish
countDownLatch.await();
// Main thread continues after all pizzas are made
outputScraper.add("All pizzas are ready!");
// Print the output
outputScraper.forEach(System.out::println);
}
}
class PizzaMaker implements Runnable {
private final List<String> outputScraper;
private final CountDownLatch countDownLatch;
public PizzaMaker(List<String> outputScraper, CountDownLatch countDownLatch) {
this.outputScraper = outputScraper;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// Simulate pizza-making process
try {
Thread.sleep(2000); // Pretend to make a pizza
outputScraper.add("Pizza made!");
countDownLatch.countDown(); // Signal completion
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Example 2: A Pool of Threads Waiting to Begin
If we start thousands of threads instead of just five, some may finish processing before others even start. To reproduce concurrency problems, we need all threads to run in parallel. Let’s modify the CountDownLatch
behavior:
// Create a CountDownLatch with an initial count of 1
CountDownLatch startLatch = new CountDownLatch(1);
// In each worker thread, wait for the start signal
// (before doing any actual work)
startLatch.await();
// Perform the actual work here
// ...
// In the main thread, release the start signal
startLatch.countDown();
4. Conclusion
The CountDownLatch
is a versatile tool for coordinating threads and ensuring synchronization in concurrent applications. Whether you’re dealing with parallel processing, testing, or other scenarios, it empowers you to manage thread interactions effectively.
Remember to use it wisely, and happy coding! 🚀
For more information, check out these resources: