要想明白CountDownLatch的实现原理,必须对AQS足够熟悉,可以参考:从ReentrantLock到AQS,到底和synchronized有啥区别
CountDownLatch:等待唤醒
中文翻译过来就是倒计时锁
作用:
用于某个线程在执行任务之前,需要等待其它线程完成一些前置任务,必须等所有的前置任务都完成,才能开始执行本线程的任务。
快速使用
就两个API :wait 和 countdown
state:还需等待几个线程
- 对于调用countDown方法的线程,不关心state具体的值
- 对于调用await方法的线程,state代表还需要等待多少个线程调用countDown方法
构造方法:指定state初始值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
await:阻塞等待执行
await对应获取资源,因为自定义了tryAcquireShared方法,只有state为0才能获取资源
countDown:state--
countDown对应释放资源,自旋CAS让state = state - 1。
当state被减为0,所有调用await阻塞的线程会按顺序一个个醒来,继续执行它们的任务
实现原理
因为也完全是依托AQS而实现的,所以只关注重写的两个模板方法
CountDownLatch是个共享锁,所以只需要重写两个方法
// State为0 获取锁成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// CAS自旋 让state--
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
应用场景
RocketMQ源码中大量出现了CountDownLatch(1)的使用,即当一个线程执行完毕,这个线程的执行结果被所有调用await的线程所依赖,我们希望所有调用await的线程都醒来,而wait/notifyAll是个独占锁就做不到这样的功能。当可能不止一个线程依赖某个线程的执行结果时,CountDownLatch(1)就能做到wait/notify做不到的。
Semaphore:控制线程数量
作用:控制「访问资源/执行某段程序」的线程数量。
快速使用
主要是acquire获取许可证,release释放许可证
permits/state:最大线程数量
即所控制的最大线程数量
构造方法:指定state/公平
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
acquire:获取许可证
无参acquire,获取一个,可以指定获取的许可证个数
release:释放许可证
类似acquire
实现原理
和CountDownLatch类似,我们只看非公平的两个模板方法吧
// CAS自旋 让state-acquires
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// CAS自旋 让state+releases
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
小结
可以看到,通过AQS我们可以快速的自定义共享/独占锁,通过这两个例子不仅仅认识了两个好用的工具类,也是更进一步理解AQS的应用。