参考文档:

https://blog.csdn.net/zxdfc/article/details/52752803

简介

CountDownLatch是一个同步辅助类。允许一个或多个线程等待其他线程完成操作。内部采用的公平锁和共享锁的机制实现

举个栗子

public class CountDownLatchTest {

    public static void main(String[] args) {
CountDownLatch cd = new CountDownLatch(2);
ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 2; i++) {
es.execute(new MyThread1(cd));
}
System.out.println("wait");
try {
cd.await();
System.out.println("two thread run over");
} catch (InterruptedException e) {
e.printStackTrace();
}
es.shutdown();
}
} class MyThread1 implements Runnable {
CountDownLatch latch;
public MyThread1(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(1000 * 3);
latch.countDown();
System.out.println(Thread.currentThread().getName() + " over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

源码分析

构造方法

 public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

函数列表

void await():如果当前count大于0,当前线程将会wait,直到count等于0或者中断。PS:当count等于0的时候,再去调用await(),线程将不会阻塞,而是立即运行。后面可以通过源码分析得到
boolean await(long timeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
void countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程
long getCount() :获得计数的数量

await分析

public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
    public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判断是否发生中断
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//注意:-1表示还有线程未执行完毕,1表示等待的线程已经执行完毕
doAcquireSharedInterruptibly(arg);
}
    protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//这里的state就是最开始new CountDownLatch(int count),count等于state
}
    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) { //如果当前线程是第二个节点(头节点表示持有锁的节点)
int r = tryAcquireShared(arg);//就判断尝试获取锁
/*
这里要注意一下r的值就2种情况-1和1:
情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待
情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点
*/
if (r >= 0) { //1表示等待的线程已经执行完毕
setHeadAndPropagate(node, r);//将当前节点设置头结点,如果还有资源则唤醒后继节点
p.next = null; //删除原来的头结点的引用,则原头结点可以gc掉
failed = false;
return;
}
}
//如果当前线程不是第二个节点,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //调用park()使线程进入waiting状态,如果被唤醒,查看自己是不是被中断的
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
    private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 记录头结点
setHead(node);//设置node为头结点
/*
*从上面传递过来 propagate = 1;
*一定会进入下面的判断
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared
if (s == null || s.isShared())
doReleaseShared();//唤醒后继节点
}
}
//唤醒后继节点
private void doReleaseShared() {
for (;;) {
Node h = head;//获得头结点
if (h != null && h != tail) {
int ws = h.waitStatus;//获取头结点的状态默认值为0
if (ws == Node.SIGNAL) {//如果等于SIGNAL唤醒状态
//将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break
if (h == head) // loop if head changed
break;
}
}

countDown分析

public void countDown() {
sync.releaseShared(1);//每次释放一个
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//尝试释放共享锁
doReleaseShared();//释放共享锁,上面有泛型不在重复
//由于state等于0,所以从队列中,从头结点一个接着一个退出(break),for循环等待,
return true;
}
return false;
}
    protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))//比较并设置state
return nextc == 0; //等于0的时候,肯定还有一些Node在队列中,所以要调用doReleaseShared(),来清理
}
}

concurrent(五)同步辅助器CountDownLatch & 源码分析的更多相关文章

  1. concurrent(六)同步辅助器CyclicBarrier & 源码分析

    参考文档:Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/3533995.html简介Cy ...

  2. Java - "JUC" CountDownLatch源码分析

    Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...

  3. CountDownLatch 源码分析

    CountDownLatch 源码分析: 1:CountDownLatch数据结构 成员变量 Sync类型对象 private final Sync sync; Sync是继承AQS的一个类,Coun ...

  4. JUC之CountDownLatch源码分析

    CountDownLatch是AbstractQueuedSynchronizer中共享锁模式的一个的实现,是一个同步工具类,用来协调多个线程之间的同步.CountDownLatch能够使一个或多个线 ...

  5. 并发工具CountDownLatch源码分析

    CountDownLatch的作用类似于Thread.join()方法,但比join()更加灵活.它可以等待多个线程(取决于实例化时声明的数量)都达到预期状态或者完成工作以后,通知其他正在等待的线程继 ...

  6. Mybatis Interceptor 拦截器原理 源码分析

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...

  7. scrapy-redis(调度器Scheduler源码分析)

    settings里面的配置:'''当下面配置了这个(scrapy-redis)时候,下面的调度器已经配置在scrapy-redis里面了'''##########连接配置######## REDIS_ ...

  8. 并发编程(五)——AbstractQueuedSynchronizer 之 ReentrantLock源码分析

    本文将从 ReentrantLock 的公平锁源码出发,分析下 AbstractQueuedSynchronizer 这个类是怎么工作的,希望能给大家提供一些简单的帮助. AQS 结构 先来看看 AQ ...

  9. 第五章 类加载器ClassLoader源码解析

    说明:了解ClassLoader前,先了解 第四章 类加载机制 1.ClassLoader作用 类加载流程的"加载"阶段是由类加载器完成的. 2.类加载器结构 结构:Bootstr ...

随机推荐

  1. [转] vue前端异常监控sentry实践

    1. 监控原理 1.1 onerror 传统的前端监控原理分为异常捕获和异常上报.一般使用onerror捕获前端错误: window.onerror = (msg, url, line, col, e ...

  2. Nginx-Tomcat 等运维常用服务的日志分割-logrotate

    目录 一 .Nginx-Tomcat 等常用服务日志分析 Nginx 日志 Tomcat日志 MongoDB 日志 Redis 日志 二 .日志切割服务 logrotate 三.日志切割示例 Ngin ...

  3. winfrom数据导出

    /// <summary> /// 数据导出 /// </summary> /// <param name="dataGridView">< ...

  4. webform 的路由

    webform是怎么通过url找到对应handler的呢? mvc 和webapi的路由都是通过注册到RouteTable.Routes中,然后在urlroutingmodule中路由到对应route ...

  5. 初学Mybatis

    首先配置mybatis配置文件 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" &qu ...

  6. EF自动创建数据库步骤之四(启用数据库初始器)

    在创建完DBIfNotExistsInitializer数据库初始化器类后,需要在程序每一次访问数据库前,告诉EF使用该初始化器进行初始化. 代码如下 : Database.SetInitialize ...

  7. HandBrake-QuickSync-Mac (内容:QuickSync encoder via VideoToolbox )

    来源:https://github.com/galad87/HandBrake-QuickSync-Mac/commit/2c1332958f7095c640cbcbcb45ffc955739d594 ...

  8. JavaWeb 发送邮件

    我们可以使用第三方的邮箱服务器来发送邮件. 常用的邮件传输协议有2种:POP3/SMTP.IMAP/SMTP. POP和IMAP的区别:在邮箱客户端的操作,比如移动邮件.标记已读,如果使用POP,是不 ...

  9. 面试官:讲讲redis的过期策略如何实现?

    时隔多日,小菜鸡终于接到阿里的面试通知,屁颠屁颠的从上海赶到了杭州. 经过半个小时的厮杀: 自我介绍 hashMap和ConcurrentHashMap区别 jdk中锁的实现原理 volatile的使 ...

  10. Python与设计模式之单例模式

    一.什么是单例 即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了 # setti ...