概述

CountDownLatch 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。简单来说,就是 CountDownLatch 内部维护了一个计数器,每个线程完成自己的操作之后都会将计数器减一,然后会在计数器的值变为 0 之前一直阻塞,直到计数器的值变为 0.

简单使用

这个例子主要演示了,如何利用 CountDownLatch 去协调多个线程同时开始运行。这个时候的 CountDownLatch 中的计数器的现实含义是等待创建的线程个数,每个线程在开始任务之前都会调用 await() 方法阻塞,直到所有线程都创建好,每当一个线程创建好后,都会提交调用 countDown() 方法将计数器的值减一 (代表待创建的线程数减一)。

public static void main(String[] args) {Test countDownLatchTest=new Test();countDownLatchTest.runThread();
}
//计数器为10,代表有10个线程等待创建
CountDownLatch countDownLatch=new CountDownLatch(10);/*** 创建一个线程* @return*/
private Thread createThread(int i){Thread thread=new Thread(new Runnable() {@Overridepublic void run() {try {//在此等待,直到计数器变为0countDownLatch.await();System.out.println("thread"+Thread.currentThread().getName()+"准备完毕"+System.currentTimeMillis());}catch (InterruptedException e){e.printStackTrace();}}});thread.setName("thread-"+i);return  thread;
}public void runThread(){ExecutorService executorService= Executors.newFixedThreadPool(10);try {for(int i=0;i<10;i++){Thread.sleep(100);executorService.submit(createThread(i));//一个线程创建好了,待创建的线程数减一countDownLatch.countDown();}}catch (InterruptedException e){e.printStackTrace();}}

下面我们就以这个例子,来解释源码:

源码分析

继承体系

从锁的分类上来讲,CountDownLatch 其实是一个” 共享锁 “。还有一个需要注意的是 CountDownLath 是响应中断的,如果线程在对锁进行操作的期间发生了中断,会直接抛出 InterruptedException。

源码分析

计数器的本质是什么?

刚才我们也提到了,CountDownLatch 中一个非常重要的东西就是计数器。那么我们首先需要分析的就是源码中哪个部分充当了计数器的角色。
我们通过构造方法来查看:
我们的代码CountDownLatch countDownLatch=new CountDownLatch(10);背后实际上是调用了下面这个方法:

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

而这个 Sync 的实例化又做了什么工作呢?

Sync(int count) {setState(count); //就是修改了AQS中的state值
}

现在已经解决了我们的第一个问题,实际上 AQS 中的 state 充当了计数器。

await 方法

  1. await 方法实际上是调用了 sync 的一个方法
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
  1. sync 的void acquireSharedInterruptibly(int arg)的实现如下
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())//如果线程中断了,则抛异常。//证明了之前所说的CountDownLatch是会响应中断的throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}
  1. 如果没有中断,就会调用tryAcquireShared(arg)
    它的实现非常的简单,如果 state 为 0,就返回 1,否则返回 - 1
protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;
}
  1. 如果 state 不为 0,就会返回 - 1,if 条件成立,就会调用doAcquireSharedInterruptibly(arg)
    这个方法的实现,稍微复杂一点,但这个方法也不陌生了,它的功能就是把该线程加入等待队列中并阻塞,但是在入队之后,不一定会立即 park 阻塞,它会判断自己是否是第二个节点,如果是就会再次尝试获取。
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); //去看一看state是否为0,步骤3分析过if (r >= 0) {//如果state目前为0,就出队setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//进入阻塞队列阻塞,如果发生中断,则抛异常throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);
}

CountDownLatch 的 await 方法比其它几个锁的实现简单得多。不过需要注意的一点就是 CountDownLatch 是会响应中断的,这一点在源码中也有多处体现。

countDown 方法

  1. countDown 方法实际上是调用 sync 中的一个方法
public void countDown() {sync.releaseShared(1);
}
  1. boolean releaseShared(int arg)的具体实现如下:
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}
  1. tryReleaseShared(arg)方法的具体实现如下:
protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {//自旋int c = getState();if (c == 0)//计数器已经都是0了,当然会释放失败咯return false;int nextc = c-1;//释放后,计数器减一if (compareAndSetState(c, nextc))//CAS修改计数器return nextc == 0;}
}

这个方法就是去尝试直接修改 state 的值。如果 state 的修改成功,且修改后的 state 值为 0,就会返回 true。就会执行doReleaseShared();方法。

  1. doReleaseShared();的实现如下,它的作用就是 state 为 0 的时候,去唤醒等待队列中的线程。
private void doReleaseShared() {/** Ensure that a release propagates, even if there are other* in-progress acquires/releases.  This proceeds in the usual* way of trying to unparkSuccessor of head if it needs* signal. But if it does not, status is set to PROPAGATE to* ensure that upon release, propagation continues.* Additionally, we must loop in case a new node is added* while we are doing this. Also, unlike other uses of* unparkSuccessor, we need to know if CAS to reset status* fails, if so rechecking.*/for (;;) { //自旋Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;if (ws == Node.SIGNAL) {if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue;            // loop to recheck casesunparkSuccessor(h);}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}
}

现在基本源码已经分析完毕了,只要理解了 AQS 和 CountDownLatch 的计数器到底是什么,就能够很好的理解 CountDownLatch 的原理了。

关注微信公众号:【入门小站】,解锁更多知识点

Java高并发之CountDownLatch源码分析相关推荐

  1. THOR:MindSpore 自研高阶优化器源码分析和实践应用

    摘要:这篇文章跟大家分享下THOR的实践应用.THOR算法的部分内容当前已经在MindSpore中开源 本文分享自华为云社区<MindSpore 自研高阶优化器源码分析和实践应用>,原文作 ...

  2. Java并发之AQS源码分析ReentranLock、ReentrantReadWriteLock、Condition

    基于AQS的独享锁和共享锁的源码分析 基本概念说明 锁的基本原理思考 测试环境 实现方案1 实现方案2 独占锁:ReentrantLock源码分析 类依赖和类成员变量说明 加锁过程,入口方法:lock ...

  3. Java并发编程之CountDownLatch源码解析

    一.导语 最近在学习并发编程原理,所以准备整理一下自己学到的知识,先写一篇CountDownLatch的源码分析,之后希望可以慢慢写完整个并发编程. 二.什么是CountDownLatch Count ...

  4. java 并发包之 LongAdder 源码分析

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. LongAdder是java8中新增的原子类,在多线程环境中,它比AtomicLong性能要高出不少 ...

  5. Java并发编程之ThreadLocal源码分析

    1 一句话概括ThreadLocal   什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象的线程创建了一个独立的变量副本. 2 ThreadLocal使用场景   用一句话总结 ...

  6. 死磕java并发cas_死磕 java并发包之AtomicInteger源码分析

    问题 (1)什么是原子操作? (2)原子操作和数据库的ACID有啥关系? (3)AtomicInteger是怎么实现原子操作的? (4)AtomicInteger是有什么缺点? 简介 AtomicIn ...

  7. Java集合篇:LinkedList源码分析

    (注:本文内容基于JDK1.6) 一.概述: LinkedList与ArrayList一样实现List接口,只是ArrayList是List接口的大小可变数组的实现,LinkedList是List接口 ...

  8. CountDownLatch 源码分析

    1. 类介绍 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待.用给定的计数 初始化 CountDownLatch.由于调用了 countDown() 方法,所以在 ...

  9. Java类集框架 —— LinkedList源码分析

    在JDK1.7之前,LinkedList是采用双向环形链表来实现的,在1.7及之后,Oracle将LinkedList做了优化,将环形链表改成了线性链表.本文对于LinkedList的源码分析基于JD ...

最新文章

  1. 如果让你拥有100万粉丝,你会做什么
  2. 小学生学python-小学生都学Python了,你还不知道如何开始
  3. Java NIO学习系列七:Path、Files、AsynchronousFileChannel
  4. css制作按钮按下去效果
  5. 计算机程序设计艺术+第3卷:排序与查找(第二版)pdf
  6. SpringCloud 微服务网关Gateway 动态路由配置
  7. TrustedInstaller.exe in Windows Vista consumes 100% CPU
  8. sencha touch 类的使用
  9. 如何使用wordnet
  10. 再也不用担心动态规划,BAT大佬精讲42道题目,相见恨晚
  11. 弱逼发福利——BZOJ简易题解
  12. UVA 12161 Ironman Race in Treeland (树分治)
  13. 【易实战】Spring Cloud Greenwich版本发布
  14. 微软Exchange Server 0Day漏洞,尽快修复
  15. ISBN码书籍信息查询
  16. Matlab二维正态分布可视化
  17. ArcGIS面转中心线
  18. java hotspot tm_Java HotSpot(TM) 64-Bit Server VM warning
  19. MySQL8.0中消失又回来的磁盘临时表
  20. Docker容器获取局域网ip(使用macvlan)

热门文章

  1. ListView提示和技巧
  2. GradView使用举例
  3. python 多线程应用
  4. L1-042 日期格式化-PAT团体程序设计天梯赛GPLT
  5. 1035. 插入与归并(25)-浙大PAT乙级真题
  6. CAT - 监控平台之装配篇
  7. Linux 安装 informix
  8. Confirm Hosts Registration with the server failed
  9. ubuntu15.04安装wps-office的64位版
  10. 隐藏Jquery dialog 按钮