Multithreaded programming often requires multiple threads to wait for one another, coordinate actions, or limit access to shared resources. Java provides powerful synchronization utilities in java.util.concurrent
to manage such scenarios.
Three of the most important coordination tools are:
Semaphore
– Controls access to resources.CountDownLatch
– Waits until other threads complete.CyclicBarrier
– Synchronizes threads at a common barrier point.
These constructs help build responsive, reliable, and well-synchronized systems in both real-time and batch-oriented applications.
🧩 Core Definitions and Purpose
Semaphore
A semaphore is like a bouncer at a club—only a limited number of people can enter at once.
Semaphore semaphore = new Semaphore(3); // 3 permits
semaphore.acquire(); // blocks if no permit
semaphore.release(); // returns permit
Use Cases:
- Limiting concurrent access to a pool of connections
- Rate limiting
- Bounded resource sharing
CountDownLatch
Think of a CountDownLatch
like a starting gate in a horse race—all threads wait until the gate opens.
CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
try {
latch.await(); // wait for count to reach 0
System.out.println("Started after all threads ready");
} catch (InterruptedException e) {}
}).start();
// In 3 other threads
latch.countDown(); // each call reduces count by 1
Use Cases:
- Waiting for multiple services to initialize
- Ensuring that a task proceeds only when others finish
CyclicBarrier
A CyclicBarrier
is a group checkpoint—all threads wait until all reach a certain point.
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached barrier");
});
Runnable task = () -> {
try {
// do some work
barrier.await(); // wait for others
} catch (Exception e) {}
};
Use Cases:
- Iterative parallel computations
- Games or simulations where players act in rounds
🔁 Thread Lifecycle and State Transitions
Each of these utilities interacts with threads in BLOCKED or WAITING states:
Semaphore.acquire()
→ BLOCKED if no permitsCountDownLatch.await()
→ WAITING until count is zeroCyclicBarrier.await()
→ WAITING until all parties reach the barrier
These transitions are key to understanding how threads synchronize.
🧠 Java Memory Model and Visibility
These classes rely on volatile
and memory fences to ensure:
- Visibility: Threads see the most recent value of counters or permits
- Atomicity: Operations like
countDown()
orrelease()
are atomic - Happens-before relationships**: Guaranteed when one thread signals another
🔗 Thread Coordination Comparison
Feature | Semaphore | CountDownLatch | CyclicBarrier |
---|---|---|---|
Reusable | Yes | No | Yes |
Trigger | release() |
countDown() |
await() |
Block until ready | acquire() |
await() |
await() |
Barrier action | No | No | Yes (optional Runnable) |
Thread-safe | Yes | Yes | Yes |
🔐 Locking and Concurrency Context
These are higher-level than locks like ReentrantLock
, but can be used together. Use them to avoid manual wait/notify and complex lock hierarchies.
🧪 Real-World Examples
1. Limiting Concurrent Access (Semaphore)
Semaphore dbSemaphore = new Semaphore(10); // max 10 DB connections
Runnable dbTask = () -> {
try {
dbSemaphore.acquire();
accessDatabase();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
dbSemaphore.release();
}
};
2. Waiting for Startup Completion (CountDownLatch)
CountDownLatch ready = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
initializeService();
ready.countDown();
}).start();
}
ready.await(); // Main thread waits here
3. Iterative Barrier Check (CyclicBarrier)
CyclicBarrier barrier = new CyclicBarrier(4, () -> System.out.println("Round complete"));
for (int i = 0; i < 4; i++) {
new Thread(() -> {
compute();
barrier.await();
}).start();
}
🕹 Java Version Tracker
📌 What's New in Java?
- Java 8
- Lambdas and streams simplify concurrency
CompletableFuture
- Java 9
Flow API
for reactive systems
- Java 11
- Minor improvements to
CompletableFuture
- Minor improvements to
- Java 17
- Pattern matching enhancements
- Java 21
- Structured Concurrency API
- Virtual Threads (Project Loom)
- Scoped Values for shared data
✅ Best Practices
- Prefer
Semaphore
for resource limiting, not flow control. - Use
CountDownLatch
for one-time events only. - Use
CyclicBarrier
when coordination happens repeatedly. - Always handle
InterruptedException
. - Avoid spin waits or polling when synchronization tools exist.
- Use
Executors
orStructuredTaskScope
with barriers and latches. - Clean up properly when interrupted.
🚫 Anti-Patterns
- Misusing Semaphore as a lock (use
Lock
instead) - Reusing CountDownLatch (it cannot be reset)
- Ignoring exceptions from
await()
- Not releasing Semaphore in
finally
block - Using sleep instead of proper coordination
🧰 Design Patterns Involving These Tools
- Worker Thread: Uses
CountDownLatch
to wait for all workers - Two-Phase Termination:
CountDownLatch
for shutdown - Phaser-based iteration: CyclicBarrier alternative with more control
📘 Conclusion and Key Takeaways
- Use Java’s coordination utilities to avoid manual synchronization pitfalls.
Semaphore
,CountDownLatch
, andCyclicBarrier
each have distinct use cases.- Understanding these tools leads to robust, scalable concurrent designs.
- Combine them with modern features like virtual threads and structured concurrency for cleaner code.
❓ FAQ
1. Can I reuse a CountDownLatch?
No. You must create a new one for each use.
2. What happens if I call await()
on CyclicBarrier but other threads don’t?
The thread will block until the barrier is full or it's reset/interrupted.
3. Is Semaphore fair by default?
No. You must pass true
in the constructor for FIFO fairness.
4. What’s the difference between lock and semaphore?
Lock is mutual exclusion (1 permit); semaphore is generalized access control (N permits).
5. Can I reset a CyclicBarrier?
Yes, using reset()
—but use with caution as it can break waiting threads.
6. Does CountDownLatch block indefinitely?
Yes, unless you use await(long timeout, TimeUnit unit)
.
7. Are these utilities thread-safe?
Yes, fully thread-safe and designed for concurrency.
8. Can I use these with virtual threads?
Yes, especially useful in Java 21 with structured concurrency.
9. How does barrier action in CyclicBarrier work?
Runs a task once all threads reach the barrier.
10. Should I prefer CompletableFuture over these tools?
For async flows, yes. For coordination, use these tools.