CountDownLatch在java并发编程实战一书中翻译为闭锁。主要的作用是一组线程等待另一组线程完成事件后调用 CountDownLatch.countDown 方法,当里面的状态 为0的时候,其他调用CountDownLatch.await方法的线程继续往下执行。
首先看下构造方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
首先判断参数再调用的都是Sync的构造方法。
Sync(int permits) {
setState(permits);
}
把你实例化的count设置给AQS的state参数,代表你要调多少次countDown方法,await才会返回。
await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
-----------------Sync--------
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
CountDownLatch直接把调用转到Sync里,Sync首先判断当前线程是否被中断,再调用tryAcquireShared,tryAcquireShared的实现在Sync里
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
Sync.tryAcquireShared是判断State是否等于0,前面在构造方法那里已经说过,实例化的时候回给State设置值,当CountDownLatch还未被调用countDown方法的时候,这里返回的是-1,接着就进入doAcquireSharedInterruptibly方法,
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
doAcquireSharedInterruptibly的实现是在AQS里,java很多并发组件都会调用这个方法,像信号量,读写锁之类的。这个方法的作用如下
- 给当前线程创建NODE节点,并标记为共享锁,在把NODE节点设置在CLH的队尾
- 获取当前线程的前继节点,判断前继节点是否是头节点,是头结点的话就去尝试获取锁,
- 调用shouldParkAfterFailedAcquire,判断当前节点是否需要被唤醒,唤醒的话就放回true,否则就移除CLH中被取消的NODE,返回false
-
调用parkAndCheckInterrupt方法,阻塞当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
await方法大致就是这些,还有些细节,并不影响,我这就不展开了
countDown
public void countDown() {
sync.releaseShared(1);
}
也是直接转给Sync,具体实现实在AQS里
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
首先尝试释放共享锁,调用成功的话在调用doReleaseShared方法释放共享锁。 tryReleaseShared是在CountDownLatch.Sync里重写了
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;
}
}
主要的功能就是把锁的state - 1,获取成功后就会调用 doReleaseShared方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
- 首先会判断 head的状态是不是SIGNAL,是SIGNAL的话就尝试调用unparkSuccessor将状态复位;即把waitStatus设置为0
- 设置成功,则唤醒下一节点,否则继续循环。
- 如果waitStatus==0 ,就尝试把waitStatus设置为PROPAGATE,
- 头结点没变化的话就跳出循环,否则继续循环
下面贴一下unparkSuccessor的代码
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
主要就是状态判断,唤醒下一线程
总结一下
CountDownLatch是一共享锁的形式实现的等待/通知功能,功能就是: 一组线程等待另一组线程完成事件后调用 CountDownLatch.countDown 方法,当里面的状态 为0的时候,其他调用CountDownLatch.await方法的线程继续往下执行。和CyclicBarrier可以重复使用不同,CountDownLatch当状态为0了,就不能变成初始化在使用了。
主要就是两方法,主线程调用Await方法等待其他线程完成任务后调用 countDown方法唤醒。
调用Await方法时有两种情况
- 获取锁成功 即 state ==0,从第一个节点开始,依次唤醒所有线程
- 获取锁失败,把当前线程制作成NODE,标记为共享锁,放在CLH的队尾
调用countDown 方法时也有两种情况
- state=0,就唤醒节点
- state!=0,把state-1,继续阻塞