概述
CountDownLatch
是 Java 并发包(java.util.concurrent
)中的一个同步辅助类。它允许一个或多个线程一直等待,直到其他线程执行的一组操作完成。
主要特点
- 计数器:
CountDownLatch
内部维护了一个计数器。 - 等待机制:当调用
await()
方法时,线程会阻塞,直到计数器变为0。 - 减一操作:每次调用
countDown()
方法时,计数器减1。
应用场景
- 等待多个线程完成某些操作:例如,主线程等待所有子线程完成初始化操作后再继续执行。
- 启动多个线程后等待所有线程都启动完毕:例如,在分布式系统中,等待所有节点启动完毕后再进行下一步操作。
实现原理
CountDownLatch
通过内部的 Sync
类来管理计数器和同步状态。Sync
继承自 AbstractQueuedSynchronizer
(AQS),利用 AQS 提供的同步机制来实现等待和通知功能。
关键方法
-
构造方法:
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
构造方法接受一个整数参数
count
,表示计数器的初始值。 -
await():
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
调用
await()
方法会使当前线程等待,直到计数器变为0。如果当前线程被中断,则抛出InterruptedException
。 -
countDown():
public void countDown() { sync.releaseShared(1); }
调用
countDown()
方法会使计数器减1。如果计数器变为0,则释放所有因调用await()
而阻塞的线程。
源码解析
/**
* CountDownLatch是一个同步辅助类,允许一个或多个线程等待直到一组操作完成。
*/
public class CountDownLatch {
/**
* Sync类继承自AbstractQueuedSynchronizer,用于实现CountDownLatch的同步机制。
*/
private static final class Sync extends AbstractQueuedSynchronizer {
/**
* 构造函数,初始化计数器的值。
* @param count 初始计数器的值
*/
Sync(int count) {
setState(count);
}
/**
* 获取当前计数器的值。
* @return 当前计数器的值
*/
int getCount() {
return getState();
}
/**
* 尝试获取共享锁,如果计数器为0则成功。
* @param acquires 获取的数量
* @return 返回1表示成功获取,返回-1表示获取失败
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* 尝试释放共享锁,减少计数器的值。
* @param releases 释放的数量
* @return 返回true如果计数器已经减少到0
*/
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
/**
* CountDownLatch的构造函数,初始化计数器。
* @param count 计数器的初始值
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* 等待直到计数器的值为0,或当前线程被中断。
* @throws InterruptedException 如果当前线程被中断
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 等待直到计数器的值为0,或直到超时。
* @param timeout 最大等待时间
* @param unit 时间单位
* @throws InterruptedException 如果当前线程被中断
* @return 如果计数器值在超时前变为0则返回true,否则返回false
*/
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 减少计数器的值,释放一个共享的锁。
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* 获取当前计数器的值。
* @return 当前计数器的值
*/
public long getCount() {
return sync.getCount();
}
}
应用案例
假设有一个应用程序需要启动多个子线程进行初始化操作,主线程需要等待所有子线程完成初始化后再继续执行。
示例代码
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int numberOfThreads = 5;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(() -> {
System.out.println("Thread " + Thread.currentThread().getName() + " is working");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown(); // 完成工作后计数器减1
}).start();
}
// 主线程等待所有子线程完成
latch.await();
System.out.println("All threads have finished their work.");
}
}
输出
Thread Thread-0 is working
Thread Thread-1 is working
Thread Thread-2 is working
Thread Thread-3 is working
Thread Thread-4 is working
All threads have finished their work.
常见问题
-
Q:
CountDownLatch
可以重用吗?- A: 不可以。一旦计数器达到0,就不能再复位。如果需要重用,可以考虑使用
CyclicBarrier
。
- A: 不可以。一旦计数器达到0,就不能再复位。如果需要重用,可以考虑使用
-
Q:
CountDownLatch
是否支持超时等待?- A: 支持。可以使用
await(long timeout, TimeUnit unit)
方法进行带超时的等待。
- A: 支持。可以使用
-
Q:
CountDownLatch
与CyclicBarrier
有什么区别?- A:
CountDownLatch
是一次性的,计数器达到0后不能再复位;而CyclicBarrier
是可重用的,可以多次重置屏障。
- A:
优缺点
优点
- 简单易用:API 简单,易于理解和使用。
- 灵活性高:适用于多种多线程同步场景。
- 性能良好:基于 AQS 实现,性能稳定。
缺点
- 不可重用:计数器一旦达到0,就不能再复位。
- 缺乏灵活性:相比于
CyclicBarrier
,CountDownLatch
在某些场景下不够灵活。
总结
CountDownLatch
是一个非常实用的同步工具,适用于需要等待多个线程完成某些操作后再继续执行的场景。通过简单的 API 和高效的实现,它能够有效地管理和协调多个线程的工作。然而,由于其一次性特性,对于需要重复使用的场景可能不太适用。在实际应用中,可以根据具体需求选择合适的同步工具。