countdownlcath
定义
CountDownLatch其实是复合名词,由单词countdown和latch复合而来。countdown是倒数的意思,而latch则是闩锁、闭锁的意思,复合词容易让人联想到预先设定一个计数值,并且"锁住(阻塞)“一些东西(线程),然后进行倒数,当数值减少到0的时候进行"放行(解除阻塞)”。
基本使用
// 构造函数,要求初始的计数值要大于零
public CountDownLatch(int count) ......
// 当前调用线程会等待直到计数值倒数为0或者线程中断
public void await() throws InterruptedException ......
// 当前调用线程会等待直到计数值倒数为0、线程中断或者超过输入的等待时间期限
public boolean await(long timeout, TimeUnit unit) throws InterruptedException ......
// 计数值减少1,当计数值减少到0所有等待线程会释放(解除等待)
public void countDown() ......
// 获取计数值
public long getCount() ......
示例
@Test
public void uploadTs() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch count = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
executorService.execute(new MyCount(count, i));
}
}
class MyCount implements Runnable {
private final CountDownLatch countDown;
private final int i;
MyCount(CountDownLatch countDown1, int i1) {
this.countDown = countDown1;
this.i = i1;
}
public void run() {
countDown.countDown();
System.out.println(" this thread " + i);
}
}
原理
在之前的aqs中 有说过 coundownlatch是通过aqs来实现的 现在来看下它到底是怎么实现的,顺便回顾下aqs
public class CountDownLatch {
// 继承aqs 对指定的方法重写,复用sqs框架的锁机制
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 设置AQS的state的值为输入的计数值
Sync(int count) {
setState(count);
}
// 获取AQS中的state属性
int getCount() {
return getState();
}
// 共享模式下获取资源,这里无视共享模式下需要获取的资源数,只判断当前的state值是否为0,为0的时候,意味资源获取成功,闭锁已经释放,所有等待线程需要解除阻塞
// 如果state当前已经为0,那么线程完全不会加入AQS同步队列中等待,表现为直接运行
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 共享模式下释放资源,这里也无视共享模式下需要释放的资源数,每次让状态值通过CAS减少1,当减少到0的时候,返回true
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 这种情况下说明了当前state为0,从tryAcquireShared方法来看,线程不会加入AQS同步队列进行阻塞,所以也无须释放
if (c == 0)
return false;
// state的快照值减少1,并且通过CAS设置快照值更新为state,如果state减少为0则返回true,意味着需要唤醒阻塞线程
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
// 输入的计数值不能小于0,意味着AQS的state属性必须大于等于0
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));
}
public void countDown() {
// 共享模式下释放资源
sync.releaseShared(1);
}
public long getCount() {
// 获取当前state的值
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
countDown
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
// 设置AQS的state的值为输入的计数值
Sync(int count) {
setState(count);
}
// 获取AQS中的state属性
int getCount() {
return getState();
}
// 共享模式下获取资源,这里无视共享模式下需要获取的资源数,只判断当前的state值是否为0,为0的时候,意味资源获取成功,闭锁已经释放,所有等待线程需要解除阻塞
// 如果state当前已经为0,那么线程完全不会加入AQS同步队列中等待,表现为直接运行
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 共享模式下释放资源,这里也无视共享模式下需要释放的资源数,每次让状态值通过CAS减少1,当减少到0的时候,返回true
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 这种情况下说明了当前state为0,从tryAcquireShared方法来看,线程不会加入AQS同步队列进行阻塞,所以也无须释放
if (c == 0)
return false;
// state的快照值减少1,并且通过CAS设置快照值更新为state,如果state减少为0则返回true,意味着需要唤醒阻塞线程
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
// 输入的计数值不能小于0,意味着AQS的state属性必须大于等于0
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));
}
public void countDown() {
// 共享模式下释放资源
sync.releaseShared(1);
}
public long getCount() {
// 获取当前state的值
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
接下来再分步解析每一个方法。先看构造函数:
// 构造函数,其实就是对AQS的state进行赋值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
// ......
}
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// ......
// volatile修饰的状态值,变更会强制写回主内存,以便多线程环境下可见
private volatile int state;
// 调用的是这个父类方法
protected final void setState(int newState) {
state = newState;
}
// ......
}
由于AQS的头尾节点都是懒创建的,所以只初始化了state的情况下,AQS是"空的"。接着看await()方法:
// CountDownLatch中
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
↓↓↓↓↓↓↓↓↓↓↓↓
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
// state等于0的时候返回1,大于0的时候返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// ......
}
↓↓↓↓↓↓↓↓↓↓↓↓
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// ......
// 共享模式下获取资源,响应中断
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程已经处于中断状态,则清空中断状态位,抛出InterruptedException
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取资源,此方法由子类CountDownLatch中的Sync实现,小于0的时候,说明state > 0
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// ......
//
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 基于当前线程新建一个标记为共享的新节点
final Node node = addWaiter(Node.SHARED);
try {
for (;;) {
// 获取新入队节点的前驱节点
final Node p = node.predecessor();
// 前驱节点为头节点
if (p == head) {
// 并且尝试获取资源成功,也就是每一轮循环都会调用tryAcquireShared尝试获取资源(r >= 0意味获取成功),除非阻塞或者跳出循环
// 由前文可知,CountDownLatch中只有当state = 0的情况下,r才会大于等于0
int r = tryAcquireShared(arg);
if (r >= 0) {
// 设置头结点,并且传播获取资源成功的状态,这个方法的作用是确保唤醒状态传播到所有的后继节点
// 然后任意一个节点晋升为头节点都会唤醒其第一个有效的后继节点,起到一个链式释放和解除阻塞的动作
setHeadAndPropagate(node, r);
// 由于节点晋升,原来的位置需要断开,置为NULL便于GC
p.next = null; // help GC
return;
}
}
// shouldParkAfterFailedAcquire -> 判断获取资源失败是否需要阻塞,这里会把前驱节点的等待状态CAS更新为Node.SIGNAL
// parkAndCheckInterrupt -> 判断到获取资源失败并且需要阻塞,调用LockSupport.park()阻塞节点中的线程实例,(解除阻塞后)清空中断状态位并且返回该中断状态
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
// ......
}
因为是工程化的代码,并且引入了死循环避免竞态条件下的异常,代码看起来比较复杂,其实做了下面几件事:
先验当前调用线程的中断状态。
state不为0的时候,当前调用线程实例会包装为一个SHARED类型的Node加入AQS的同步等待队列的尾部,并且通过LockSupport.park()阻塞节点中的线程实例。
state为0的时候,直接返回,放行线程(可以尝试使用new CountDownLatch(0))。
死循环中如果tryAcquireShared(arg)返回值大于等于0,则任意一个晋升为头节点的节点解除阻塞后都会链式唤醒后继(正常的)节点,唤醒的逻辑在setHeadAndPropagate()方法中,这个方法命名其实有点奇怪,直译为"设置头节点和传播"。
细心一看,唤不唤同步等待队列中的阻塞节点,只取决于tryAcquireShared(arg)方法的返回值是否大于0,往前一推敲,取决于state或者说count是否为0,所以可知必定有使state变小的方法
接着看countDown()方法:
// CountDownLatch中
public void countDown() {
sync.releaseShared(1);
}
↓↓↓↓↓↓↓↓↓↓↓↓
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
// 共享模式下释放资源,这里也无视共享模式下需要释放的资源数,每次让状态值通过CAS减少1,当减少到0的时候,返回true
protected boolean tryReleaseShared(int releases) {
// 减少计数值state,直到变为0,则进行释放
for (;;) {
int c = getState();
// 如果已经为0,直接返回false,不能再递减到小于0,返回false也意味着不会进入AQS的doReleaseShared()逻辑
if (c == 0)
return false;
int nextc = c - 1;
// CAS原子更新state = state - 1
if (compareAndSetState(c, nextc))
// 如果此次递减为0则返回true
return nextc == 0;
}
}
// ......
}
↓↓↓↓↓↓↓↓↓↓↓↓
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// ......
// 共享模式下,释放arg个资源
public final boolean releaseShared(int arg) {
// 从上面的分析来看,这里只有一种可能返回true并且进入doReleaseShared()方法,就是state由1递减为0的时候
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
// 共享模式下的释放操作
private void doReleaseShared() {
// 死循环是避免因为新节点入队产生影响,CAS做状态设置被放在死循环中失败了会在下一轮循环中重试
for (;;) {
Node h = head;
// 头不等于尾,也就是AQS同步等待队列不为空
// h == NULL,说明AQS同步等待队列刚进行了初始化,并未有持有线程实例的节点
if (h != null && h != tail) {
int ws = h.waitStatus;
// 头节点为Node.SIGNAL(-1),也就是后继节点需要唤醒,CAS设置头节点状态-1 -> 0,并且唤醒头节点的后继节点(也就是紧挨着头节点后的第一个节点)
if (ws == Node.SIGNAL) {
// 这个if分支是对于Node.SIGNAL状态的头节点,这种情况下,说明
// 这里使用CAS的原因是setHeadAndPropagate()方法和releaseXX()方法都会调用此doReleaseShared()方法,CAS也是并发控制的一种手段
// 如果CAS失败,很大可能是头节点发生了变更,需要进入下一轮循环更变头节点的引用再进行判断
// 该状态一定是由后继节点为当前节点设置的,具体见shouldParkAfterFailedAcquire()方法
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后继节点,如果有后继节点被唤醒,则后继节点会调用setHeadAndPropagate()方法,更变头节点和转播唤醒状态
unparkSuccessor(h);
}
// 头节点状态为0,说明头节点的后继节点未设置前驱节点的waitStatus为SIGNAL,代表无需唤醒
// CAS更新它的状态0 -> Node.PROPAGATE(-3),这个标识目的是为了把节点状态设置为跟Node.SIGNAL(-1)一样的负数值,
// 便于某个后继节点解除阻塞后,在一轮doAcquireSharedInterruptibly()循环中调用shouldParkAfterFailedAcquire()方法返回false,实现"链式唤醒"
else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果头节点未发生变化,则代表当前没有其他线程获取到资源,晋升为头节点,直接退出循环
// 如果头节点已经发生变化,代表已经有线程(后继节点)获取到资源,
if (h == head) // loop if head changed
break;
}
}
// 解除传入节点的第一个后继节点的阻塞状态,当前处理节点的等待状态会被CAS更新为0
private void unparkSuccessor(Node node) {
// 当前处理的节点状态小于0则直接CAS更新为0
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
// 如果节点的第一个后继节点为null或者等待状态大于0(取消),则从等待队列的尾节点向前遍历,
// 找到最后一个(这里指的是队列尾部->队列头部搜索路径的最后一个满足的节点,一般是传入的node节点的next节点)不为null,并且等待状态小于等于0的节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
// 解除传入节点的后继节点的阻塞状态,唤醒后继节点所存放的线程
if (s != null)
LockSupport.unpark(s.thread);
}
// ......
}
await
// CountDownLatch中
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
↓↓↓↓↓↓↓↓↓↓↓↓
// 私有静态内部类Sync中
private static final class Sync extends AbstractQueuedSynchronizer {
// state等于0的时候返回1,大于0的时候返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// ......
}
↓↓↓↓↓↓↓↓↓↓↓↓
// AbstractQueuedSynchronizer中
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// ......
// 共享模式下获取资源,响应中断
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果线程已经处于中断状态,则清空中断状态位,抛出InterruptedException
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取资源,此方法由子类CountDownLatch中的Sync实现,小于0的时候,说明state > 0
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// ......
//
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 基于当前线程新建一个标记为共享的新节点
final Node node = addWaiter(Node.SHARED);
try {
for (;;) {
// 获取新入队节点的前驱节点
final Node p = node.predecessor();
// 前驱节点为头节点
if (p == head) {
// 并且尝试获取资源成功,也就是每一轮循环都会调用tryAcquireShared尝试获取资源(r >= 0意味获取成功),除非阻塞或者跳出循环
// 由前文可知,CountDownLatch中只有当state = 0的情况下,r才会大于等于0
int r = tryAcquireShared(arg);
if (r >= 0) {
// 设置头结点,并且传播获取资源成功的状态,这个方法的作用是确保唤醒状态传播到所有的后继节点
// 然后任意一个节点晋升为头节点都会唤醒其第一个有效的后继节点,起到一个链式释放和解除阻塞的动作
setHeadAndPropagate(node, r);
// 由于节点晋升,原来的位置需要断开,置为NULL便于GC
p.next = null; // help GC
return;
}
}
// shouldParkAfterFailedAcquire -> 判断获取资源失败是否需要阻塞,这里会把前驱节点的等待状态CAS更新为Node.SIGNAL
// parkAndCheckInterrupt -> 判断到获取资源失败并且需要阻塞,调用LockSupport.park()阻塞节点中的线程实例,(解除阻塞后)清空中断状态位并且返回该中断状态
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
```
- 先验当前调用线程的中断状态。
- state不为0的时候,当前调用线程实例会包装为一个SHARED类型的Node加入AQS的同步等待队列的尾部,并且通过LockSupport.park()阻塞节点中的线程实例。
- state为0的时候,直接返回,放行线程(可以尝试使用new CountDownLatch(0))。
- 死循环中如果tryAcquireShared(arg)返回值大于等于0,则任意一个晋升为头节点的节点解除阻塞后都会链式唤醒后继(正常的)节点,唤醒的逻辑在setHeadAndPropagate()方法中