基本概念
CountDownLatch 这个类能够使一个或多个线程等待其他线程完成各自的工作后再执行。
例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
调用方法
假设有工人、老板两种角色。工人负责工作,老板负责检查工人已完成的工作。那么存在以下条件:老板必须等待工人完成工作后才能进行检查。
- 工人
public class Worker implements Runnable {
private CountDownLatch cdl;
private String name;
public Worker(CountDownLatch cdl, String name) {
this.cdl = cdl;
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name + " 开始工作 ...");
Thread.sleep(1000);
System.out.println(name + " 结束工作...");
cdl.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 老板
// 老板类
class Boss implements Runnable {
private CountDownLatch cdl;
private String name;
public Boss(CountDownLatch cdl, String name) {
this.cdl = cdl;
this.name = name;
}
@Override
public void run() {
try {
cdl.await();
System.out.println(name + " 检查工作 ...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 调用过程
public class Test {
public static void main(String[] args) {
final CountDownLatch cdl = new CountDownLatch(2);
new Thread(new Boss(cdl, "大老板")).start();
new Thread(new Worker(cdl, "工人甲")).start();
new Thread(new Worker(cdl, "工人乙")).start();
}
}
- 输出结果
工人甲 开始工作 ...
工人乙 开始工作 ...
工人甲 结束工作...
工人乙 结束工作...
大老板 检查工作 ...
内部构造
- 构造函数,CountDownLatch 在构建时需要指定计数值(count)。
// 内部类
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) {
// 抛出异常...
}
this.sync = new Sync(count);
}
- 同步器,CountDownLatch 中定义了一个继承自 AQS 的 Sync,并用 AQS 的状态值表示计数值。
Sync(int count) {
setState(count);
}
- 计数值,即 count,类似于 ReetrantLock 的重入计数。只有在 count 为 0 时,才能触发释放锁的操作。
countDown
递减操作,调用该方法的线程会递减 CountDownLatch 的计数值。
// 递减锁的计数(若计数到达零,则释放所有等待的线程)
public void countDown() {
sync.releaseShared(1);
}
调用过程如下:
AQS.releaseShared->CountDownLatch.tryReleaseShared->AQS.doReleaseShared
重点来看 tryReleaseShared 方法,该方法在 sync 作具体实现:
public boolean tryReleaseShared(int releases) {
for (;;) {
// 校验计数值
int c = getState();
if (c == 0) {
return false;
}
// 递减计数值,若计数为 0,则唤醒所有通过 await 操作加入同步等待队列的线程
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
return nextc == 0;
}
}
}
await
等待操作,调用该方法的线程会进入阻塞状态,直到 CountDownLatch 的计数值为 0 时,该线程才会被唤醒。
public void await() throws InterruptedException {
// 获取共享锁,并且不忽略
sync.acquireSharedInterruptibly(1);
}
调用过程如下:
AQS.acquireSharedInterruptibly ->CountDownLatch.tryAcquireShared ->AQS.doAcquireSharedInterruptibly
重点来看 tryAcquireShared 方法,该方法在 sync 作具体实现:
// Sync
public int tryAcquireShared(int acquires) {
// count 为 0 表示成功获取锁
return getState() == 0 ? 1 : -1;
}
总结
CountDownLatch 内部采用了共享锁来实现。
- count :计数值可以理解为通过锁的重入计数。
- countDown :表示释放锁,每次操作都会递减锁的重入计数,只有重入计数为 0 时,才能成功释放锁,否则返回 false。
- await :表示获取锁,只有在重入计数为 0 时,才可以成功获取锁,否则进入阻塞状态。