参考文档:

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. 反弹Shell原理及检测技术研究

    1. 反弹Shell的概念本质 所谓的反弹shell(reverse shell),就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端. 本文会先分别讨论: ...

  2. Redis 实战搭建高可用架构

    前言:最近在看关于redis缓存方面的知识,今天就来个 Redis sentinel 高可用架构,实战开始之前,先看看sentinel的概念 什么是redis-sentinel Redis-Senti ...

  3. new/delete与malloc/free的区别

    new/delete与malloc/free的区别 参考: https://blog.csdn.net/u013485792/article/details/51329541 https://www. ...

  4. redis集群cluster简单设置

    环境: 这里参考官方使用一台服务器:Centos 7  redis-5.0.4    192.168.10.10 redis集群cluster最少要3个主节点,所以本次需要创建6个实例:3个主节点,3 ...

  5. 如何在Mac下配置多个Java版本

    使用工具:brew cask brew cask是一个用命令行管理Mac下应用的工具,提供了自动安装和卸载功能,能够自动从官网上下载并安装 最新的版本,它是基于homebrew的一个增强工具. 一. ...

  6. ElasticSearch(九)e代驾使用Elasticsearch流程设计(Yii1版本)

    一.控制器层的更新.添加.删除 class AddKnowledgeAction extends CAction { //add and update public function actionPo ...

  7. Python 字符串多替换时性能基准测试

    结论 先说结果, 直接替换是最好的. replace 一层层用, 方法笨了一点, 还可以. 时间消耗: tx2 < tx3 < tx1 < tx4 t2 < t3 < t ...

  8. Livy 安装教程

    Livy 安装教程 本文原始地址:https://sitoi.cn/posts/16143.html 安装环境 Fedora 29 Spark PySpark 安装步骤 下载 Livy 安装包 解压 ...

  9. DVWA的搭建

    DVWA的搭建 一.DVWA是什么? 一款渗透测试演练系统,俗称靶机. 二.如何搭建? Linux有成套的靶机,直接打开使用就可以,下面开始介绍Windows 下DVWA的搭建. 运行phpstudy ...

  10. python基本数据类型的时间复杂度

    1.list 内部实现是数组 2.dict 内部实现是hash函数+哈希桶.一个好的hash函数使到哈希桶中的值只有一个,若多个key hash到了同一个哈希桶中,称之为哈希冲突. 3.set 内部实 ...