1、CountDownLatch的作用:允许一个或多个线程等待其他线程完成操作。
意思就是使用CountDownLatch来阻塞一条或多条线程,当CountDownLatch的计数被线程减到0后,就唤醒阻塞的一条或多条线程。
2、CountDownLatch的使用案例:
案例一:使用一个计数为3的CountDownLatch 实例阻塞两条线程T1、T2,随机创建3条线程,每条线程去将计数-1,当3变成0的时候,就会唤醒阻塞的T1、T2线程。
public class CountDownLatchDemo {//创建一个计数为3的CountDownLatch实例public static final CountDownLatch countDownLatch = new CountDownLatch(3);public static void main(String[] args) throws InterruptedException {new Thread(()->{try {//使用countDownLatch阻塞当前线程。countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("T1 wake........");},"T1").start();new Thread(()->{try {//使用countDownLatch阻塞当前线程。countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("T2 wake........");},"T2").start();for (int i = 0; i < 3; i++) {new Thread(() -> {try {Thread.sleep(2000L);} catch (InterruptedException e) {e.printStackTrace();}//当前线程做计数-1操作,当3变成0的时候,阻塞的线程唤醒(可阻塞多个线程)。countDownLatch.countDown();}).start();}}
}
结果输出:
T1 wake........
T2 wake........
使用案例二:使用一个计数为1的CountDownLatch 去阻塞10条线程,使用main线程去将计数-1,就会唤醒10条线程。
public class CountDownLatchDemo1 {public static final CountDownLatch countDownLatch = new CountDownLatch(1);public static void main(String[] args) throws InterruptedException {for (int i=0;i<10;i++){new Thread(()->{try {countDownLatch.await();System.out.println(Thread.currentThread().getName()+"-----wake......");} catch (InterruptedException e) {e.printStackTrace();}},"T"+i).start();}Thread.sleep(1000L);countDownLatch.countDown();}
}
输出结果:
T0-----wake......
T3-----wake......
T9-----wake......
T2-----wake......
T6-----wake......
T5-----wake......
T1-----wake......
T7-----wake......
T4-----wake......
T8-----wake......
3、CountDownLatch的实现原理分析:
根据使用的方式我们可以看出,CountDownLatch类的await()方法阻塞线程、countDown()方法会将计数-1。当计数为0的时候唤醒被阻塞的所有线程。
单从CountDownLatch的表现出来的特性上来看,跟我们之前说的Condition特别类似,那么CountDownLatch的实现方式是使用Condition的等待队列来实现的吗?还有一种可能就是CountDownLatch也可能是使用AQS的同步队列来实现的,因为AQS也满足阻塞线程+唤醒线程的特性。
AQS 还是 Condition?????????
这个问题我们通过看源码的方式来确定一下:我们先看CountDownLatch的await()方法。
3.1、CountDownLatch的类结构:通过类结果其实我们就能看出其实CountDownLatch的实现是AQS同步器。
public class CountDownLatch {//静态的内部同步类,实现了AQSprivate 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;}}}//同步器private final Sync sync;//构造函数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));}//将计数-1public void countDown() {sync.releaseShared(1);}public long getCount() {return sync.getCount();}public String toString() {return super.toString() + "[Count = " + sync.getCount() + "]";}
}
3.2、CountDownLatch的构造函数:
public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);}Sync(int count) {//设置AQS的state标记为CountDownLatch的计数setState(count);}
3.3、CountDownLatch的await()方法实现:
public void await() throws InterruptedException {//使用同步器获取申请一个队列位置,构建的node 模式是shared 模式的即共享模式,这个过程会先检查是否中断。sync.acquireSharedInterruptibly(1);}//接上面 sync.acquireSharedInterruptibly(1); 入参arg=1public final void acquireSharedInterruptibly(int arg)throws InterruptedException {//先检查是否中断,如果是直接抛出InterruptedException异常。if (Thread.interrupted())throw new InterruptedException();//尝试获取一个资源,这个理主要是判断AQS中的state(CountDownLatch的计数)是否为0 ,如果为0返回1 否则返回-1.if (tryAcquireShared(arg) < 0)//如果CountDownLatch的计数不为0,那就以共享可中断模式获取资源,doAcquireSharedInterruptibly(arg);}//接 doAcquireSharedInterruptibly(arg);private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {//构建一个链表的节点模式是SHARED,如果需要初始化队列,那就初始化。final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {//获取AQS的head节点final Node p = node.predecessor();if (p == head) {//如果是head节点,就判断CountDownLatch的计数是否为0int r = tryAcquireShared(arg);if (r >= 0) {//如果CountDownLatch的计数是0,那就以迭代的形式去逐步唤醒队列中的所有线程。setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}/*如果不是head节点,或者是head节点但是CountDownLatch的计数不等于0,那就阻塞当前线程,并将节点的waitState改为-1 SIGNAL状态。线程会阻塞在此处,等待CountDownLatch的计数变为0后唤醒。唤醒后此处区分于ReentrantLock,因为唤醒后此处不会自我阻塞。而是直接抛出异常。*/if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
3.4、上面跟踪到CountDownLatch.await()方法说道线程阻塞在parkAndCheckInterrupt()方法的地方等待被唤醒,接下来我们看看是如何唤醒的。我们来看看CountDownLatch.countDown()方法。
public void countDown() {//使用同步来释放资源sync.releaseShared(1);}//接 sync.releaseShared(1); 入参arg=1public final boolean releaseShared(int arg) {//将CountDownLatch的计数-1(AQS的state),当等于0的时候返回true,否则返回falseif (tryReleaseShared(arg)) {//如果CountDownLatch的计数=0 那就去释放阻塞的线程。doReleaseShared();return true;}return false;}private void doReleaseShared() {/*自旋来唤醒head节点的next节点,上面我们说了当等待的线程节点,只要有一个呗唤醒,就会以迭代的形式逐步唤醒后面的线程,也就是说前一个节点的线程唤醒后一个节点的线程。那么也就是 说,当某线程调用countDown()方法后发现计数为0 了,那么当前线程只会唤醒head节点的next节 点的线程,然后next节点的线程去唤醒下一个节点的线程,以次传递下去。因此在此方法中只会唤醒head节点的next节点中的线程就会结束自旋。*/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;}}
3.5、上面跟踪到了doReleaseShared的方法中会唤醒head节点的next的线程,那么阻塞的线程就会允许,继续自己的下一次循环,我们来分析一下被唤醒的线程的下一次循环:
private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {//head节点的next节点的线程 来获取前一个节点,肯定是head了,没有疑问。final Node p = node.predecessor();if (p == head) {//去判断countDownLatch的计数是否为0,当然是0了,这是我们的前提条件嘛,没有疑问。int r = tryAcquireShared(arg);if (r >= 0) {//countDownLatch的计数为0了,去更换head节点,并唤醒下一个节点的线程,依次传递下去,直到将所有的线程唤醒位置。setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//通过最后一个调用countDown()的方法在此处唤醒了的head节点的next节点的线程,进入下一次循环。throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
到此CountDownLatch的原理分析结束。