目录
- CountDownLatch 简介
- CountDownLatch 使用示例
- CountDownLatch 优缺点
- CountDownLatch 源码解析
- 扩展
CountDownLatch 简介
CountDownLatch 是一个同步工具类,用来协调多个线程之间的同步,达到线程间通信的效果。CountDownLatch 允许一个或多个线程一直等待,直到其他线程运行完成后再执行。
使用示例
- 让多个线程等待
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
System.out.println("运动员["+Thread.currentThread().getName()+"]准备完毕,等待号令...");
countDownLatch.await();
System.out.println("运动员["+Thread.currentThread().getName()+"]开跑...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("裁判准备发令...");
Thread.sleep(2000);
countDownLatch.countDown();
System.out.println("执行发令...");
countDownLatch.countDown();
}
}
输出结果:
裁判准备发令...
运动员[Thread-0]准备完毕,等待号令...
运动员[Thread-1]准备完毕,等待号令...
运动员[Thread-2]准备完毕,等待号令...
运动员[Thread-3]准备完毕,等待号令...
运动员[Thread-4]准备完毕,等待号令...
执行发令...
运动员[Thread-0]开跑...
运动员[Thread-2]开跑...
运动员[Thread-4]开跑...
运动员[Thread-3]开跑...
运动员[Thread-1]开跑...
- 让一个线程等待
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println("运动员["+Thread.currentThread().getName()+"]到达终点...");
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("工作人员开始统计排名...");
}
}
输出结果:
运动员[Thread-0]到达终点...
运动员[Thread-1]到达终点...
运动员[Thread-2]到达终点...
运动员[Thread-3]到达终点...
运动员[Thread-4]到达终点...
工作人员开始统计排名...
优缺点
CountDownLatch 是一次性的,计算器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用。
源码解析
CountDownLatch 是基于 AQS 同步器的共享锁实现的内部逻辑,通过代码可以看到,CountDownLatch 类主要的几个方法都是通过 Sync 类实现的,而 Sync 类就是 AQS 的实现类。
public class CountDownLatch {
// AQS 同步器的实现类
private final Sync sync;
// 构造方法,初始化计数器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 阻塞加共享锁,可响应中断
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 阻塞加共享锁,可响应中断,并设置超时时间
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 计数器减 1,调用释放共享锁方法
public void countDown() {
sync.releaseShared(1);
}
}
Sync 继承了 AQS 并实现了共享锁相关的两个方法 tryAcquireShared 及 tryReleaseShared
private 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) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
大体结构基本清楚了,现在开始理流程。首先需要创建 CountDownLatch 对象,并设置一个初始计数器值,最终是调用了 AQS 的 setState 方法将该值设置到了 AQS 的 state 同步状态值上。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
然后主线程会执行 await() 方法进行等待,这个方法最终调用了 AQS 的 acquireSharedInterruptibly 方法执行加共享锁,acquireSharedInterruptibly 方法首先会调用 tryAcquireShared 方法尝试加锁,如果尝试失败,则进入等待队列并挂起线程。因为上一步初始化了 state,很明显这里 state != 0,所以加锁失败,线程进入等待队列。
// 阻塞加共享锁,可响应中断
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 加共享锁
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 加共享锁
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
接着子线程会执行 countDown() 方法,这个方法调用 AQS 的 releaseShared 方法释放共享锁,其实就是让 AQS 的同步状态减 1,当同步状态值减到 0 时,需要唤醒上一步在等待队列中挂起的线程,此时这些线程就可以加锁成功,从而执行后续的操作。
// 计数器减 1,调用释放共享锁方法
public void countDown() {
sync.releaseShared(1);
}
// 释放共享锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 当同步状态减至 0 时,唤醒等待队列中的线程
doReleaseShared();
return true;
}
return false;
}
// 释放共享锁
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc)) // 通过 cas 让同步状态减 1
return nextc == 0; // 如果减 1 后的状态为 0 返回 true
}
}
扩展
CountDownLatch 与 Thread.join
CountDownLatch 的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似 join() 方法。CountDownLatch 可以手动控制在 n 个线程里调用 n 次 countDown() 方法使计数器进行减一操作,也可以在一个线程里调用 n 次执行减一操作。而 join() 的实现原理是不停检查 join 线程是否存活,如果 join 线程存活则让当前线程永远等待。所以 CountDownLatch 使用起来较为灵活。
CountDownLatch 与 CyclicBarrier
两者都能够实现线程之间的等待,只不过它们侧重点不同:
- CountDownLatch 一般用于一个或多个线程,等待其他线程执行完任务后,再才执行。
- CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。
- CountDownLatch是减计数,计数减为0后不能重用;而CyclicBarrier是加计数,可置0后复用。
CyclicBarrier 的源码分析请看下篇