CountDownLatch分析
1. 属性分析
很简单,就一个Sync属性,CountDownLatch是基于AQS来实现的。一般来说,基于AQS实现的锁或者一些别的东西,里面都有个继承与AQS的静态内部类,功能的实现都是委托给这个静态内部类来实现的。这里也是。
private final Sync sync;
2. 构造方法
count合法性检查,创建同步器(Sync)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
3. 主要方法分析
await
和这个类似的带等待超时的await。这里只列举一个,说明情况。
调用这个方法会使的当前线程一直等待,一直等待count变为0,或者这个线程被中断掉。如果当前的count等于0,方法会立即返回(也就是当前线程不会等待)。如果count大于0,当前线程就会等待。(其实就是被park住了),如果count变为0,或者当前线程被中断。这个方法才会返回。
public void await() throws InterruptedException {
// await就是获取一个锁,注意,这里锁的模式是共享锁,在AQS里面,如果没有获取到锁,就会被park住。如果count变为0,
sync.acquireSharedInterruptibly(1);
}
getCount
返回当前的count值。
public long getCount() {
return sync.getCount();
}
countDown
countDown就是释放锁
public void countDown() {
sync.releaseShared(1);
}
到这里,看到所有的功能是委托给Sync来实现的。下面就看看Sync具体是怎么做的
4. Sync分析
看代码,真没有多少。下面就一点点来分析是怎么做的。
也就是说,每次countDown。就是归还锁,但是Sync重写了tryReleaseShared
,理论上来说应该先获取锁,然后再释放锁,但是这里不是,先释放锁,tryReleaseShared
如果返回为true表示,释放锁成功,应该唤醒等待的节点。所以,在tryReleaseShared
里面,只有当state为0的时候才会返回true,其余的都是--。
对于await方法来说,调用的是获取锁的操作,因为在AQS里面获取锁失败会使的当前线程park住。同样的tryAcquireShared
方法返回值大于等于0 表示当前线程获取到锁,反之表示当前线程没有获取到锁,需要park。在Sync里面,只有在State变为0的时候,才会返回1。表示获取到锁。当前线程就返回。如果没有获取到锁,就阻塞。只能等待countDown了。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 在构造方法里面调用AbstractQueuedSynchronizer里面的setState方法,State表示count。
Sync(int count) {
setState(count);
}
// 直接返回State
int getCount() {
return getState();
}
// 如果state不是0,就返回1.表示获取到锁。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
// 拿到state
int c = getState();
// 如果c等于0.说明不能堵塞了。直接返回
if (c == 0)
return false;
int nextc = c-1;
// 重新设置state
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
这里会涉及到一个概念 AQS的独占模式和共享模式
独占模式
同一时刻,只有一个线程可以获取到锁(互斥)
共享模式
同步时刻,多个线程可以获取到锁,这个多个线程获取锁的是不一定的。(共享)
举个列子说明两个的一个小差别
@Test
public void testCCountDownLatch() throws InterruptedException {
// CountDownLatch值为2.
CountDownLatch countDownLatch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep((int) Math.random() * 10);
logMessage("start");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 当state变为0之后,这四个线程会连续唤醒,如果是独占模式,就不可能发生这种情况,只能唤醒一个。
for (int i = 0; i < 4; i++) {
countDownLatch.await();
logMessage("begin" + i);
}
}
public static void logMessage(Object o) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(new Date()) + "-" + Thread.currentThread().getName() + ":" + o);
}
额外补充
看上面内容的时候突然在org.apache.rocketmq.common
也有一个CountDownLatch2
。很奇妙,来看看他有什么特别的地方。
public class CountDownLatch2 {
private final CountDownLatch2.Sync sync;
public CountDownLatch2(int count) {
if (count < 0) {
throw new IllegalArgumentException("count < 0");
} else {
this.sync = new CountDownLatch2.Sync(count);
}
}
public void await() throws InterruptedException {
this.sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return this.sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
this.sync.releaseShared(1);
}
public long getCount() {
return (long)this.sync.getCount();
}
public void reset() {
this.sync.reset();
}
public String toString() {
return super.toString() + "[Count = " + this.sync.getCount() + "]";
}
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
private final int startCount;
Sync(int count) {
this.startCount = count;
this.setState(count);
}
int getCount() {
return this.getState();
}
protected int tryAcquireShared(int acquires) {
return this.getState() == 0 ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
int c;
int nextc;
do {
c = this.getState();
if (c == 0) {
return false;
}
nextc = c - 1;
} while(!this.compareAndSetState(c, nextc));
return nextc == 0;
}
protected void reset() {
this.setState(this.startCount);
}
}
}
百分之90都是一样的,这里增加了一个reset
方法。用于将state变为一开始的值。也就是说,在countDown一段之后,可以重新将state设置为初始状态。重新设置值在CountDownLatch
里面是不支持的。这个值只有在new的时候才能设置,别的地方没有这个操作。
关于CountDownLatch的分析就分析到这里了。 如有不正确的地方,欢迎指出。谢谢。