举例:火箭发射倒计时
一、定义
countDownLatch从字面上理解,countDown倒计时的意思latch是锁、门栓的意思,那么countDownLatch就是倒计时的门栓。
JDK1.5中提供了JAVA.util.concurrent.CountDownLatch类,用于一个或多个类等待一直到其他线程完成一系列操作。
CountDownLatch创建时设置一个count值,表示倒计时的次数,然后等待状态的线程调用CountDownLatch的await()方法进行等待,倒计时的方法是countDown(), 每次countDown都会减少count的值,直到count为0,则所有的await()的线程都会从等待中返回。
二、使用
我们使用员工下班值班人员锁门案例进行演示
for (int i=1;i<=6;i++){
new Thread(() -> {
System.out.println("第"+Thread.currentThread().getName()+"t 位员工离开房间");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName()+"t 值班人员最后关门走人");
如果我们不使用CountDownLatch 这样就会导致员工还没有全部离开房间,这个时候管理员把门锁上。
运行结果
没有使用CountDownLatch 运行结果
使用CountDownLatch代码如下
CountDownLatch countDownLatch = new CountDownLatch(6);for (int i=1;i<=6;i++){
new Thread(() -> {
System.out.println("第"+Thread.currentThread().getName()+"t 位员工离开房间"); countDownLatch.countDown(); },String.valueOf(i)).start();}
countDownLatch.await();System.out.println(Thread.currentThread().getName()+"t 值班人员最后关门走人");
运行结果:
使用CountDownLatch运行结果
三、原理
在上面我们看到,CountDownLatch主要使用CountDown方法进行减1的操作,使用await方法进行等到操作。
1、CountDown原理
CountDownLatch里面保存了一个count值,通过减1操作,直到为0时候,等待线程才可以执行。而且通过源码也可以看到这个countDown方法其实是通过sync调用releaseShared(1)来完成的。
sync是个什么,releaseShared方法又是如何实现的。我们不妨接着看源码,在CountDownLatch的开头我们找到了答案,原来这个sync在这里定义了。
在这里我们发现继承了AbstractQueuedSynchronizer(AQS)。AQS的其中一个作用就是维护线程状态和获取释放锁。在这里也就是说CountDownLatch使用AQS机制维护锁状态。而releaseShared(1)方法就是释放了一个共享锁。
现在理解了吧,底层使用AQS机制调用releaseShared方法释放一个锁资源。
那么等待的方法是如何实现的呢?
2、await原理
这两个方法都是让线程等待,一个有时间限制、一个没有时间限制
await()方法底层主要是显示acquireSharedInterruptibly()方法来实现的
首先acquireSharedInterruptibly方法里面有两个if语句,第一个判断是否被中断,如果被中断了,那就抛出中断异常,然后判断是否还有线程未执行,如果有那就执行。
tryAcquireShared方法就是判断countDown是否减到了0,如果到了0,那就返回1,不需要等待,如果没有到0 说明还有未执行的线程,继续等待所有线程执行结束
doAcquireSharedInterruptibly方法是如何实现的
大致意思我可以描述一下,他会用一个一个的节点将线程串起来 等达到条件后再一个一个的唤醒。核心就是第三行的addWaiter函数。我们可以再跟进去看看吧。
这里面使用cas机制。
对于CountDownLatch来说原理主要还是通过源码来认识。不过CountDownLatch看起来虽然很好用,也有很多不足之处,比如说CountDownLatch是一次性的 , 计数器的值只能在构造方法中初始化一次 , 之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后 , 它不能再次被使用。