【原创】Java并发编程系列22 | 倒计时器CountDownLatch
2021-03-15 04:33
标签:调度 使用步骤 如何 区别 复习 port 总结 java并发编程 bat 点击上方“java进阶架构师”,选择右上角“置顶公众号” CountDownLatch的作用是让线程等待其它线程完成一组操作后才能执行,否则就一直等待。 控制台输出: 总结CountDownLatch的使用步骤:(比如线程A需要等待线程B和线程C执行后再执行) CountDownLatch是通过一个计数器来实现的,当我们在new 一个CountDownLatch对象的时候需要带入该计数器值,该值就表示了线程的数量。每当一个线程完成自己的任务后,计数器的值就会减1。当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程继续执行了。 CountDownLatch只有一个属性Sync,Sync是继承了AQS的内部类。 2.2 await() await()是将当前线程阻塞,理解await()的原理就是要弄清楚await()是如何将线程阻塞的。 创建CountDownLatch时设置AQS.state=count,可以理解成锁被重入了count次。await()方法获取锁时锁被占用了,只能阻塞。 2.3 countDown() countDown()方法是将count-1,如果发现count=0了,就唤醒阻塞的线程。 CountDownLatch用于一个线程A需要等待另外多个线程(B、C)执行后再执行的情况。 【原创】01|开篇获奖感言 之前,给大家发过三份Java面试宝典,这次新增了一份,目前总共是四份面试宝典,相信在跳槽前一个月按照面试宝典准备准备,基本没大问题。 看到这里,证明有所收获 【原创】Java并发编程系列22 | 倒计时器CountDownLatch 标签:调度 使用步骤 如何 区别 复习 port 总结 java并发编程 bat 原文地址:https://blog.51cto.com/15009303/2552595收录于话题
#进阶架构师 | 并发编程专题
12个
20大进阶架构专题每日送达
并发编程中常遇到这种情况,一个线程需要等待另外多个线程执行后再执行。遇到这种情况你一般怎么做呢?今天就介绍一种JDk提供的解决方案来优雅的解决这一问题,那就是倒计时器CountDownLatch。本文将分以下两部分介绍:
CountDownLatch的使用
CountDownLatch源码分析1. CountDownLatch的使用
举个开会的例子:
老板先进入会议室准备材料
等待5个员工陆续进入会议室
员工到齐开始开会
老板不能一来就开会,必须要等员工都到了再开会,用CountDownLatch实现如下:
public class CountDownLatchTest {
private static CountDownLatch countDownLatch = new CountDownLatch(5);
// Boss线程,等待员工到齐开会
static class BossThread extends Thread {
@Override
public void run() {
System.out.println("Boss进入会议室准备材料...");
System.out.println("Boss在会议室等待...");
try {
countDownLatch.await(); // Boss等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Boss等到人齐了,开始开会...");
}
}
// 员工到达会议室
static class EmpleoyeeThread extends Thread {
@Override
public void run() {
System.out.println("员工" + Thread.currentThread().getName()
+ ",到达会议室....");
countDownLatch.countDown();
}
}
public static void main(String[] args) {
// Boss线程启动
new BossThread().start();
// 员工到达会议室
for (int i = 0; i
Boss进入会议室准备材料...
Boss在会议室等待...
员工Thread-2,到达会议室....
员工Thread-3,到达会议室....
员工Thread-4,到达会议室....
员工Thread-1,到达会议室....
员工Thread-5,到达会议室....
Boss等到人齐了,开始开会...
创建CountDownLatch对象,设置要等待的线程数N(这里是2);
等待线程A调用await()挂起;
线程B执行后调用countDown(),使N-1;线程C执行后调用countDown(),使N-1;
调用countDown()后检查N=0了,唤醒线程A,在await()挂起的位置继续执行。2. CountDownLatch源码分析
2.1 类结构
创建CountDownLatch时传入一个count值,count值被赋值给AQS.state。
CountDownLatch是通过AQS共享锁实现的,AQS这篇文章中详细讲解了AQS独占锁的原理,AQS共享锁和独占锁原理只有很细微的区别,这里大致介绍下:
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count
await()调用的就是AQS获取共享锁的方法。当AQS.state=0时才能获取到锁,由于创建CountDownLatch时设置了state=count,此时是获取不到锁的,所以调用await()的线程挂起并构造成结点进入AQS阻塞队列。
/**
* CountDownLatch.await()调用的就是AQS获取共享锁的方法acquireSharedInterruptibly()
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 获取共享锁
* 如果获取锁失败,就将当前线程挂起,并将当前线程构造成结点加入阻塞队列
* 判断是否获取锁成功的方法由CountDownLatch的内部类Sync实现
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg)
countDown()调用AQS释放锁的方法,每次将state减1。当state减到0时是无锁状态了,就依次唤醒AQS队列中阻塞的线程来获取锁,继续执行逻辑代码。
/**
* CountDownLatch.await()调用的就是AQS释放共享锁的方法releaseShared()
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* 释放锁
* 如果锁被全部释放了,依次唤醒AQS队列中等待共享锁的线程
* 锁全部释放指的是同一个线程重入了N次需要N次解锁,最终将state变回0
* 具体释放锁的方法由CountDownLatch的内部类Sync实现
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 释放锁,由CountDownLatch的内部类Sync实现
doReleaseShared(); // 锁全部释放之后,依次唤醒等待共享锁的线程
return true;
}
return false;
}
/**
* CountDownLatch.Sync实现AQS释放锁的方法
* 释放一次,将state减1
* 如果释放之后state=0,表示当前是无锁状态了,返回true
*/
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
// state每次减1
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;// state=0时,无锁状态,返回true
}
}
总结
创建CountDownLatch时设置一个计数器count,表示要等待的线程数量。线程A调用await()方法后将被阻塞,线程B和线程C调用countDown()之后计数器count减1。当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程A继续执行了。
CountDownLatch是由AQS实现的,创建CountDownLatch时设置计数器count其实就是设置AQS.state=count,也就是重入次数。await()方法调用获取锁的方法,由于AQS.state=count表示锁被占用且重入次数为count,所以获取不到锁线程被阻塞并进入AQS队列。countDown()方法调用释放锁的方法,每释放一次AQS.state减1,当AQS.state变为0时表示处于无锁状态了,就依次唤醒AQS队列中阻塞的线程来获取锁,继续执行逻辑代码。
参考资料
《Java并发编程之美》
《Java并发编程实战》
《Java并发编程的艺术》并发系列文章汇总
【原创】02|并发编程三大核心问题
【原创】03|重排序-可见性和有序性问题根源
【原创】04|Java 内存模型详解
【原创】05|深入理解 volatile
【原创】06|你不知道的 final
【原创】07|synchronized 原理
【原创】08|synchronized 锁优化
【原创】09|基础干货
【原创】10|线程状态
【原创】11|线程调度
【原创】12|揭秘 CAS
【原创】13|LockSupport
【原创】14|AQS 源码分析
【原创】15|重入锁 ReentrantLock
【原创】16|公平锁与非公平锁
【原创】17|读写锁八讲(上)
【原创】18|读写锁八讲(下)
【原创】19|JDK8新增锁StampedLock
【原创】20|StampedLock源码解析
《java面试宝典5.0》(初中级)
《350道Java面试题:整理自100+公司》(中高级)
《资深java面试宝典-视频版》(资深)
《Java[BAT]面试必备》(资深)
分别适用于初中级,中高级,资深级工程师的面试复习。
内容包含java基础、javaweb、mysql性能优化、JVM、锁、百万并发、消息队列,高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper、数据结构、限流熔断降级等等。
获取方式:点“在看”,V信关注上述单号并回复 【面试】即可领取,更多精彩陆续奉上。
必须点个在看支持呀,喵
文章标题:【原创】Java并发编程系列22 | 倒计时器CountDownLatch
文章链接:http://soscw.com/essay/64816.html