Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
201: CountDownLatch and CyclicBarrier
I've seen this exact scenario play out in a dozen different code reviews. A developer is trying to coordinate the startup of several background services—database connections, cache warming, and API handshakes—and they want the main application to wait until everything is ready. They use a CountDownLatch, which is the right tool for the job, but they implement it in a way that creates a ticking time bomb in production.
public class AppBootstrapper {
private final CountDownLatch latch = new CountDownLatch(3);
public void init() throws InterruptedException {
new Thread(() -> initDatabase()).start();
new Thread(() -> initCache()).start();
new Thread(() -> initApi()).start();
System.out.println("Waiting for services...");
latch.await();
System.out.println("All systems go!");
}
private void initDatabase() {
try {
// Simulate DB logic
Thread.sleep(1000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void initCache() {
// Imagine a bug here that throws a RuntimeException
throw new RuntimeException("Cache connection failed!");
latch.countDown(); // This line is unreachable
}
private void initApi() {
latch.countDown();
}
}
The Forever-Hang in Service Bootstrapping
If you run this code, you'll notice something frustrating: the program just stops. It prints "Waiting for services..." and then sits there forever. Why? Because initCache() threw a RuntimeException before it ever reached latch.countDown().
The main thread is calling await(), which tells it: "Don't move an inch until the counter hits zero." Since the cache service crashed, the counter stays at 1. Your application is now in a deadlock state, not because of a circular dependency, but because of a missing signal. This is a classic "leaked" latch.
Ensuring Completion with Finally Blocks
To fix this, you have to treat countDown() as a mandatory signal, regardless of whether the operation succeeded or failed. If a service fails to initialize, you still need to tell the main thread that the attempt is over, otherwise you're just freezing the entire JVM.
private void initCache() {
try {
// Simulate logic that might fail
throw new RuntimeException("Cache connection failed!");
} catch (Exception e) {
System.err.println("Cache failed: " + e.getMessage());
} finally {
// This ensures the main thread is always released
latch.countDown();
}
}
By wrapping the signal in a finally block, you guarantee the latch reaches zero. Now, the main thread can wake up, realize something went wrong (perhaps by checking a status flag), and shut down gracefully instead of hanging indefinitely.
Syncing Iterative Phases with CyclicBarrier
Now, CountDownLatch is great for a "starting gun" or a "waiting room," but it's a one-shot deal. Once the count hits zero, the latch is spent. You can't reset it.
I often get asked: "What if I have a process that happens in rounds?" For example, imagine a simulation where three threads perform a calculation, then they all have to stop and merge their results before anyone is allowed to start the next round. This is where CyclicBarrier comes in. Unlike the latch, the barrier is reusable.
Here is the mental model: A CountDownLatch is a gate that opens once. A CyclicBarrier is a meeting point where everyone must arrive before the group can move forward together.
public class SimulationRunner {
// 3 threads must reach the barrier before any can proceed
private final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("--- Round complete. Merging results... ---");
});
public void runSimulation() {
for (int i = 0; i < 3; i++) {
new Thread(new Worker(barrier)).start();
}
}
static class Worker implements Runnable {
private final CyclicBarrier barrier;
Worker(CyclicBarrier b) { this.barrier = b; }
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " working on Round 1");
barrier.await(); // Wait for others
System.out.println(Thread.currentThread().getName() + " working on Round 2");
barrier.await(); // Wait for others again!
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
Notice the second barrier.await(). That's the "cyclic" part. The barrier automatically resets itself after the last thread arrives, allowing your workers to synchronize again and again across multiple iterations.
📋 Practical Task
Build a Multi-Stage Log Aggregator
You need to build a system that processes a large log file in three distinct stages: Filtering, Transformation, and Aggregation. To ensure data integrity, all threads must finish the Filtering stage before any thread is allowed to start the Transformation stage, and so on.
Requirements:
- Create a
LogProcessorclass that spawns 4 worker threads. - Use a
CyclicBarrierto synchronize the workers. - The barrier should take a
Runnable(the "barrier action") that prints exactly which stage has just been completed (e.g., "Filtering complete. Moving to Transformation..."). - Each worker should simulate work in three stages using
Thread.sleep()andbarrier.await(). - Ensure that no worker prints "Starting Transformation" until all workers have printed "Finished Filtering".
There are no comments for now.