一、CountDownLatch概述
CountDownLatch
构造一个用给定计数初始化的并发计数器,能够协调多个线程之间的同步,当前线程在计数器倒计数至零之前一直等待,除非线程被中断。一般用于流程控制之类的场景,大流程分成多个子流程,子流程全部结束后大流程开始操作。
二、使用示例
使用 CountDownLatch
控制主线程等待所有子线程执行完成之后继续执行。
public static void main(String[] args) throws InterruptedException {
int n = 4;
CountDownLatch latch = new CountDownLatch(n);
for (int i = 0; i < n; i++) {
new Thread(() -> {
System.out.println("子线程执行");
latch.countDown();
}).start();
}
latch.await();
System.out.println("子线程执行完成");
}
输出结果:
子线程执行
子线程执行
子线程执行
子线程执行
子线程执行完成
三、执行原理
CountDownLatch
内部是通过共享锁的原理实现的,其中包含一个继承自 AbstractQueuedSynchronizer
的 Sync
类,当 CountDownLatch
初始化时就会给锁状态 state
指定一个初始的值(可以理解为锁的重入次数)。
调用 await
方法就会直调 AbstractQueuedSynchronizer
获取共享锁的方法 acquireSharedInterruptibly
,因为初始已经是有一个初始值了,所以会被阻塞。
调用 countDown
方法就会直调 AbstractQueuedSynchronizer
释放共享锁的方法 releaseShared
执行一次释放锁操作,当锁状态到达 0 则锁表示已经被完全释放了,这时候 await
阻塞的所有线程都被唤醒。
使用共享锁才能让所有被
await
阻塞的线程同时唤醒,如果是独占锁则只会唤醒一个线程。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 设置初始化的锁状态
Sync(int count) {
setState(count);
}
// 取得获取锁次数
int getCount() {
return getState();
}
// 尝试获取共享锁,如果锁状态为0则返回1表示获取锁成功,否则获取锁失败
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 进行一次锁释放
protected boolean tryReleaseShared(int releases) {
// CAS有并发冲突的可能,需要循环获取
for (;;) {
int c = getState();
if (c == 0)
return false;
// 锁状态-1
int nextc = c-1;
// CAS进行锁释放
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
关于 AbstractQueuedSynchronizer
中共享锁和独占锁流程的实现,见锁系列文章。