CountDownLatch 与 CyclicBarrier 有点相似。
用给定的计数初始化 CountDownLatch。由于调用了 #countDown() 方法,所以在当前计数到达零之前,#await() 方法会一直受阻塞。之后,会释放所有等待的线程,#await() 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier 。
CountDownLatch 是通过一个计数器来实现的,当我们在 new 一个 CountDownLatch 对象的时候,需要带入该计数器值,该值就表示了线程的数量。
每当一个线程完成自己的任务后,计数器的值就会减 1 。 当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程继续执行了。 虽然,CountDownLatch 与 CyclicBarrier 有那么点相似,但是他们还是存在一些区别的:
java.util.concurrent.CountDownLatch 结构如下图:
通过上面的结构图我们可以看到,CountDownLatch 内部依赖 Sync 实现,而 Sync 继承 AQS 。
CountDownLatch 仅提供了一个构造方法,代码如下:
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } 复制代码
构造一个用给定计数初始化的 CountDownLatch 。
sync 变量,为 CountDownLatch 的一个内部类 Sync ,其定义如下:
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; Sync(int count) { setState(count); } // 获取同步状态 int getCount() { return getState(); } // 获取同步状态 @Override protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } // 释放同步状态 @Override protected boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } } } 复制代码
CountDownLatch 提供 #await() 方法,来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断,定义如下:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } 复制代码
该方法内部使用 AQS 的 #acquireSharedInterruptibly(int arg) 方法,代码如下:
// AQS.java public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } 复制代码
在内部类 Sync 中重写了 #tryAcquireShared(int arg)方法,代码如下:
// Sync.java @Override protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } 复制代码
getState() 方法,获取同步状态,其值等于计数器的值。从这里我们可以看到,如果计数器值不等于 0,则会调用 #doAcquireSharedInterruptibly(int arg) 方法。该方法为一个自旋方法会尝试一直去获取同步状态,代码如下:
// AQS.java 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) { /** * 对于CountDownLatch而言,如果计数器值不等于0,那么r 会一直小于0 */ 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); } } 复制代码
CountDownLatch 提供 #await(long timeout, TimeUnit unit) 方法,来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断,或者等待超时,定义如下:
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } 复制代码
调用 AQS 的 tryAcquireSharedNanos(int acquires, long nanosTimeout) 方法。
CountDownLatch 提供 #countDown() 方法,递减锁存器的计数。如果计数到达零,则唤醒所有等待的线程。
public void countDown() { sync.releaseShared(1); } 复制代码
内部调用 AQS 的 #releaseShared(int arg) 方法,来释放共享锁同步状态:
// AQS.java public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } 复制代码
tryReleaseShared(int arg) 方法,被 CountDownLatch 的内部类 Sync 重写,代码如下:
// Sync.java @Overrride protected boolean tryReleaseShared(int releases) { for (;;) { //获取锁状态 int c = getState(); //c == 0 直接返回,释放锁成功 if (c == 0) return false; //计算新“锁计数器” int nextc = c-1; //更新锁状态(计数器) if (compareAndSetState(c, nextc)) return nextc == 0; } } 复制代码
public long getCount() { return sync.getCount(); } 复制代码
CountDownLatch 内部通过共享锁实现。
示例仍然使用开会案例。老板进入会议室等待 5 个人全部到达会议室才会开会。所以这里有两种线程:老板等待开会线程、员工到达会议室线程:
public class CountDownLatchTest { private static CountDownLatch countDownLatch = new CountDownLatch(5); /** * Boss线程,等待员工到达开会 */ static class BossThread extends Thread{ @Override public void run() { System.out.println("Boss在会议室等待,总共有" + countDownLatch.getCount() + "个人开会..."); try { //Boss等待 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("所有人都已经到齐了,开会吧..."); } } // 员工到达会议室线程 static class EmpleoyeeThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName() + ",到达会议室...."); //员工到达会议室 count - 1 countDownLatch.countDown(); } } public static void main(String[] args){ //Boss线程启动 new BossThread().start(); for(int i = 0 ; i < countDownLatch.getCount() ; i++){ new EmpleoyeeThread().start(); } } } 复制代码
运行结果: