首先CountDownLatch是JUC(java.util-concurrent)下面的并发编程工具类,JDK1.5才出现的。
CountDownLatch
是一个倒计时工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
生活中的场景有:开会场景,咱们要等全部人都到期后才开会,所以来一个人空位总数就减一,直到空位为0时,就开始开会。
直接上demo代码:
/** * @author lawt * @date 2018-09-10 9:18 * CountDownLatch使用案例 * 模拟开会场景,当所有人到场后就开始开会 **/ public class CountDownLatchDemo { /** * 参会人员必须要20个 */ private static final int THREAD_TOTAL_NUM = 20; /** * 计数器 */ private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(THREAD_TOTAL_NUM); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < THREAD_TOTAL_NUM; i++) { int index = i + 1; new Thread(() -> { try { System.out.println("第" + index + "个人到场"); //这个人到会花的时间 Thread.sleep(new Random().nextInt(3000)); } catch (Exception ex) { ex.printStackTrace(); } //第index人员到场 COUNT_DOWN_LATCH.countDown(); }).start(); } //检查是否全部人员都到场 COUNT_DOWN_LATCH.await(); System.out.println("人员都已经到场,可以开会了"); } }
运行结果:
如果仅仅是使用,到此就可以了,如果想了解深一点可以继续往下看:
CountDownLatch的几个方法
await():如果当前count大于0,当前线程将会wait,直到count等于0或者中断。PS:当count等于0的时候,再去调用await(),线程将不会阻塞,而是立即运行。后面可以通过源码分析得到。
await(long timeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
getCount() :获得计数的数量
CountDownLatch的构造函数
然后调用Sync的构造方法,使用AQS的state表示计数count
再到AQS中的setState()方法
另外state的定义volatile修饰的,说明具有可见性,就是当一个线程修改CountDownLatch(num)中的num时,其他线程是可见的。
countDown方法
如果当前count大于0,则减一,
await方法
AQS:acquireSharedInterruptibly(int arg)
CountDownLatch$Sync:int tryAcquireShared(int acquires)
这里的state就是最开始new CountDownLatch(int count),count等于state
如果获取共享锁继续调用doAcquireSharedInterruptibly(arg)
for (;;) {//本质是等待共享锁的释放,死循环。
int r = tryAcquireShared(arg);//就判断尝试获取锁
这里要注意一下r的值就2种情况-1和1:
r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待
r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点
当前节点不是头结点,当前线程一直等待,直到获取到共享锁
如果这里多个线程wait之间没有调用countDown(),线程都在等待
如下图:
释放共享锁,通知后面的节点。AQS中的doReleaseShared方法,这一块主要是AQS中的代码,之前已经讲过了,所以这里不重提了。