CountDownLoad是在并发编程中使用较多的一个类,可以完成多个线程之间的相互等待和协作,源码内容不多但功能强大且使用场景复杂多样。
源码中对CountDownLoad功能的定义非常简单:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
允许一个或多个线程等待的同步辅助在其他线程中执行的一组操作完成。
简单的说CountDownLoad实现了一个计数器的功能,使用CountDownLoad的时候需要设置一个值作为初始化使用。这个值也就是CountDownLoad在计数的时候最终所需要达到的值。
public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);
}
CountDownLoad只有一个构造函数,就是说没有无参构造函数,必须要在初始化的时候就指定count值。count值必须要大于0,否则抛出IllegalArgumentException非法参数。count值会被传给Syuc类。
Syuc是CountDownLoad的静态内部类,继承自AbstractQueuedSynchronizer,final修饰不可被继承。
private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}
}
Syuc初始化的时候会调用setState(count)方法,这个方法来自于父类。
protected final void setState(int newState) {state = newState;
}
其中的state变量,是父类AbstractQueuedSynchronizer的成员变量,volatile修改保证可见性,让多线程的情况下也能获取到最新的值。
private volatile int state;
getCount()获取最新的state值,判断与预先设置的值还差多少。
tryAcquireShared(int acquires)方法,判断state是否归零,也就是CountDownLoad设置的预期值是否已经达到。
tryReleaseShared(int releases)方法,尝试释放掉线程(这里的并真的进行释放,仅仅意味这个线程可以被释放了),如果无需释放返回false,如果还有需要释放的线程返回false,如果释放最后一个需要释放的线程则返回true。如果释放线程失败,将会一直循环并尝试释放线程,直到释放掉一个线程。
tryReleaseShared采用了compareAndSetState(int expect, int update)方法,将状态变为关闭,采用CAS原理,增加其性能。
protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
CountDownLoad中的await()方法,阻塞当前线程,直到count值为0
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
await()方法会通过Syuc的父类的acquireSharedInterruptibly(int arg)来尝试占用这个线程,造成堵塞(通过tryAcquireShared(arg)方法实现)
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly(arg)方法则会对状态作出判断,如果当前计数为0,则理解返回。如果当前计数大于零,线程被禁止调度,并且一直睡眠,直到count值归0或者当前线程被其他线程中断。
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) {//如果前一个节点为head节点,按照FIFO的原则,可以直接尝试获取锁。int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r); //获取这个节点并且将它放到AQS的队列列头处,AQS列头处的节点表示正在获取锁的节点p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) && //检查下是否需要将当前节点挂起parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
这里需要补充一下AQS队列是一个双向队列,节点中存储在next和pre变量分别指向前一个节点和后一个节点,每个节点中都包含一个线程和一个表示节点类型的变量:这个变量可以表示是独占节点还是共享节点。节点头中的线程表示占有锁的线程,其他节点中线程则等待获取锁。
await(long timeout, TimeUnit unit)类似于await(),可以设置等待时间,当等待时间过期之后,线程变继续运行。
countDown()方法,会调用releaseShared(int arg)方法,会先尝试获取一个线程并且释放他,tryReleaseShared()之前有说过只有当所有线程都被释放的瞬间才会为true。
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}
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 casesunparkSuccessor(h);}else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue; // loop on failed CAS}if (h == head) // loop if head changedbreak;}
}
总结: 多个线程调用await()方法被阻塞在一个链表里面,然后这些线程会逐一调用countDown()方法,每调用一次count值便减1,直接count为0这些线程将会被释放