CountDownLatch is a synchronization aid class in Java's concurrency package (
java.util.concurrent
). It allows one or more threads to wait for a set of operations to complete.
1. Design Concept
CountDownLatch is implemented based on AQS (AbstractQueuedSynchronizer). Its core idea is to maintain a countdown, where the waiting threads will only continue when the countdown reaches zero. The main design goal is to allow multiple threads to coordinate and complete a set of tasks.
Constructor and Counter
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
The count
passed during the construction of CountDownLatch determines the initial value of the counter, which controls the release of threads.
Core Operations Supported by AQSAQS is the foundation of CountDownLatch, implemented through a custom internal class
Sync
.Sync
inherits AQS and provides the necessary methods. The key operations are:
acquireShared(int arg)
: If the counter value is zero, it means all tasks are completed, and the thread will be granted permission.releaseShared(int arg)
: Each timecountDown()
is called, the counter decreases. When the counter reaches zero, AQS releases all waiting threads.
Implementation Details
countDown()
: CallsreleaseShared()
to decrease the counter and notify waiting threads.await()
: CallsacquireSharedInterruptibly(1)
. If the counter is not zero, the thread will block and wait.
2. Underlying Principles
The core of CountDownLatch is based on the AbstractQueuedSynchronizer (AQS), which manages the counter's state. AQS is the foundation for many synchronization tools in JUC, implemented through a synchronizer queue in both exclusive and shared modes for thread management and scheduling. CountDownLatch uses AQS's shared lock mechanism to control multiple threads waiting for a condition.
Shared Mode in AQSAQS provides two synchronization modes: exclusive and shared. CountDownLatch uses the shared mode:
Exclusive Mode: Only one thread can hold the lock at a time, such as
ReentrantLock
.Shared Mode: Allows multiple threads to share the lock, such as
Semaphore
andCountDownLatch
.
The await()
and countDown()
methods in CountDownLatch correspond to AQS's acquireShared()
and releaseShared()
operations. acquireShared()
checks the synchronization state (counter value), and if it is zero, the thread returns immediately; otherwise, it blocks and enters the waiting queue. releaseShared()
is used to decrease the counter and wake up all waiting threads.
Design of the
Sync
Inner ClassCountDownLatch implements synchronization logic through a private inner classSync
.Sync
inherits from AQS and overrides thetryAcquireShared(int arg)
andtryReleaseShared(int arg)
methods.
static final class Sync extends AbstractQueuedSynchronizer { Sync(int count) { setState(count); } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } protected boolean tryReleaseShared(int releases) { // Spin decrement counter for (;;) { int c = getState(); if (c == 0) return false; int nextc = c - 1; if (compareAndSetState(c, nextc)) return nextc == 0; } } }
tryAcquireShared(int)
: Returns 1 (success) when the counter is zero, otherwise returns -1 (block).tryReleaseShared(int)
: Each call tocountDown()
decreases the counter. When the counter reaches zero, it returnstrue
, waking up all blocked threads.
CAS Operations Ensure Thread SafetyThe
tryReleaseShared
method uses CAS (compare-and-set) to update the counter, avoiding the overhead of locks. CAS operations are supported by CPU primitives (e.g., thecmpxchg
instruction), achieving efficient, non-blocking operations. This design ensures the thread safety ofcountDown()
, allowing multiple threads to concurrently decrease the counter.Internal ConditionObjectCountDownLatch is not reusable because AQS's
ConditionObject
is designed for a one-time trigger. Once the counter reaches zero, CountDownLatch cannot be reset, and all threads are released, without resetting the initial counter value. This is the fundamental reason for its non-reusability.
3. Application Scenarios
Waiting for Multiple Threads to Complete Tasks: CountDownLatch is often used in scenarios where multiple threads need to finish their tasks before proceeding, such as batch processing tasks.
Parallel Execution and Aggregation: In data analysis or computation-intensive tasks, tasks are split into multiple sub-tasks for parallel execution, with the main thread waiting for all sub-tasks to complete before aggregating the results.
Multi-Service Dependency Coordination: When one service depends on multiple others, CountDownLatch can be used to synchronize calls across services, ensuring that all dependent services are ready before executing the main task.
4. Example Code
The following example demonstrates how to use CountDownLatch to implement a mechanism where the main thread waits for all child tasks to complete.
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { private static final int TASK_COUNT = 5; private static CountDownLatch latch = new CountDownLatch(TASK_COUNT); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < TASK_COUNT; i++) { new Thread(new Task(i + 1, latch)).start(); } // Main thread waits for all tasks to complete latch.await(); System.out.println("All tasks completed, proceeding with main thread tasks"); } static class Task implements Runnable { private final int taskNumber; private final CountDownLatch latch; Task(int taskNumber, CountDownLatch latch) { this.taskNumber = taskNumber; this.latch = latch; } @Override public void run() { try { System.out.println("Sub-task " + taskNumber + " started"); Thread.sleep((int) (Math.random() * 1000)); // Simulate task execution time System.out.println("Sub-task " + taskNumber + " completed"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { latch.countDown(); // Decrease counter when a task completes } } } }
5. Comparison with Other Synchronization Tools
CyclicBarrier
Principle and Use: CyclicBarrier also allows a group of threads to wait for each other until all threads reach a barrier point. It is suitable for multi-stage tasks or aggregating results after each stage.
Underlying Implementation: CyclicBarrier is implemented using
ReentrantLock
andCondition
, and the barrier count can be reset, allowing it to be reused.Comparison with CountDownLatch: CyclicBarrier is reusable, making it suitable for repeated synchronization, while CountDownLatch is one-time use. CountDownLatch is more flexible, allowing any thread to call
countDown()
, whereas CyclicBarrier requires specified threads to reach the barrier.Semaphore
Principle and Use: Semaphore is mainly used to control the concurrent number of resources accessed, such as limiting access to a database connection pool.
Underlying Implementation: Semaphore is based on AQS’s shared mode, similar to CountDownLatch but allows resource control through a set number of “permits”.
Comparison with CountDownLatch: Semaphore can dynamically increase/decrease permits, while CountDownLatch only decrements. Semaphore is suitable for controlling access limits, while CountDownLatch is used for synchronizing count-down points.
Phaser
Principle and Use: Phaser is an enhanced version of CyclicBarrier, allowing dynamic adjustment of the number of participating threads. It is suitable for multi-stage task synchronization and can add/remove participating threads at any time.
Underlying Implementation: Phaser includes a counter for managing the number of participating threads in each phase, allowing tasks to dynamically register or unregister.
Comparison with CountDownLatch: Phaser is more suitable for complex scenarios that require flexible control of phases and threads, while CountDownLatch is simpler and used for one-time synchronization.
6. Summary
CountDownLatch is a lightweight, non-reusable countdown synchronizer suitable for simple, one-time thread coordination. Its AQS-based shared lock implementation ensures efficient concurrent thread management and counter updates. While it lacks reusability, its simplicity makes it ideal for scenarios requiring the completion of multiple tasks.
Compared to other JUC tools:
CyclicBarrier is better for multi-stage synchronization and stage-based aggregation.
Semaphore is suited for resource access control with a controllable number of permits.
Phaser is more flexible and suitable for dynamic participation and complex multi-stage tasks.
Choosing the right synchronization tool depends on the task's nature, the dynamic involvement of threads, and whether synchronization control needs to be reused.