前言

相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行。

案例

用一个最简单的案例引出我们的主角

public class CountDownLatchDemo {

    public void run(CountDownLatch countDownLatch) {
System.out.println(Thread.currentThread().getName() + "就位");
countDownLatch.countDown();
} public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
CountDownLatch countDownLatch = new CountDownLatch(5);
IntStream.rangeClosed(0, 4)
.forEach(num -> executorService
.execute(() -> countDownLatchDemo.run(countDownLatch))
); countDownLatch.await();
System.out.println("已到齐");
} /**
* 输出:
* pool-1-thread-2就位
* pool-1-thread-5就位
* pool-1-thread-4就位
* pool-1-thread-3就位
* pool-1-thread-1就位
* 已到齐
*/
}

源码分析

看源码前最好先熟悉下 AQS 的大致结构,之前有两篇文章仅供参考,大致熟悉下即可

Java读源码之ReentrantLock

Java读源码之ReentrantLock(2)

在看 AQS 的 Node 节点的时候看到有共享模式和独占模式,ReentrantLock 用了独占模式,CountDownLatch 正式用了共享模式,相信看完能够对 AQS 有更深的理解。

初始化

  • CountDownLatch#CountDownLatch
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
// 可以看到 CountDownLatc 内部也实现了一个 AQS
this.sync = new Sync(count);
}
  • CountDownLatch.Sync#Sync
Sync(int count) {
// 直接拿了 count 把锁(当前线程可重入 Sync 锁 count 次)
setState(count);
}

等待

  • CountDownLatch#await
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
  • AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 注意,持有锁的线程被中断是直接抛异常的
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared很简单,如果全员到齐了返回1,其他时候都返回 -1
if (tryAcquireShared(arg) < 0)
// 所以没到齐前都会以共享模式入同步队列
doAcquireSharedInterruptibly(arg);
}
  • AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// addWaiter之前看过,作用就是把节点放到同步队列的末尾,但是这里节点类型是共享模式
// 值得注意的是,模式是存在节点的 nextWaiter 中,所以不管 nextWaiter 可能三种情况 1。独占模式的空节点 2.共享模式的空节点 3。Condition条件队列的下一个节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
// 下面的自旋和 acquireQueued 方法基本一模一样,重点看下区别
try {
for (;;) {
final Node p = node.predecessor();
// 如果前驱节点是 head 说明没人排队
if (p == head) {
// 再次尝试
int r = tryAcquireShared(arg);
// 只有调用了足够次数countDown,栅栏才会打开
if (r >= 0) {
// 这里的 r 一定为 1,会一个一个唤醒所有节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 一般入队肯定有人排队的,之前也看过,主要作用,通知前驱节点,然后挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
  • AbstractQueuedSynchronizer#setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// propagate = 1 次判断一定为 true
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// CountDownLatch节点所有节点都是共享模式,一定满足
if (s == null || s.isShared())
// 直接唤醒下一个
doReleaseShared();
}
}
  • AbstractQueuedSynchronizer#doReleaseShared
private void doReleaseShared() {
for (;;) {
Node h = head;
// 同步队列不为空则进入
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果有节点需要被唤醒
if (ws == Node.SIGNAL) {
// 不断重试 CAS 吧 头节点设为全新
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒下一个节点
unparkSuccessor(h);
}
// 到这里 下一个已经唤醒了,把节点状态设置为 PROPAGATE,说明是共享状态唤醒的
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 直到头节点变化结束,也就是下一个一个被唤醒了,然后再由下一个接着唤醒
if (h == head)
break;
}
}

签到

看等待过程,栅栏打开后,所有共享模式的节点会一个一个的唤醒,让我们一起看看如何打开栅栏并唤醒第一个节点。

  • AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 这个方法和等待时候自悬的一样,用于唤醒第一个节点
doReleaseShared();
return true;
}
return false;
}
  • CountDownLatch#tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
// c == 0 说明栅栏已经打开过了,CountDownLatch 是一次性的,直接false
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
// cas 递减状态,达到0的时候返回 true 栅栏打开
return nextc == 0;
}
}

Java读源码之CountDownLatch的更多相关文章

  1. Java读源码之ReentrantLock

    前言 ReentrantLock 可重入锁,应该是除了 synchronized 关键字外用的最多的线程同步手段了,虽然JVM维护者疯狂优化 synchronized 使其已经拥有了很好的性能.但 R ...

  2. Java读源码之ReentrantLock(2)

    前言 本文是 ReentrantLock 源码的第二篇,第一篇主要介绍了公平锁非公平锁正常的加锁解锁流程,虽然表达能力有限不知道有没有讲清楚,本着不太监的原则,本文填补下第一篇中挖的坑. Java读源 ...

  3. Java读源码之Thread

    前言 JDK版本:1.8 阅读了Object的源码,wait和notify方法与线程联系紧密,而且多线程已经是必备知识,那保持习惯,就从多线程的源头Thread类开始读起吧.由于该类比较长,只读重要部 ...

  4. Java读源码之ThreadLocal

    前言 JDK版本: 1.8 之前在看Thread源码时候看到这么一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal实现的是 ...

  5. Java读源码之Object

    前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...

  6. Java读源码之LockSupport

    前言 JDK版本: 1.8 作用 LockSupport类主要提供了park和unpark两个native方法,用于阻塞和唤醒线程.注释中有这么一段: 这个类是为拥有更高级别抽象的并发类服务的,开发中 ...

  7. java读源码 之 map源码分析(HashMap,图解)一

    ​ 开篇之前,先说几句题外话,写博客也一年多了,一直没找到一种好的输出方式,博客质量其实也不高,很多时候都是赶着写出来的,最近也思考了很多,以后的博客也会更注重质量,同时也尽量写的不那么生硬,能让大家 ...

  8. java读源码 之 queue源码分析(PriorityQueue,附图)

    今天要介绍的是基础容器类(为了与并发容器类区分开来而命名的名字)中的另一个成员--PriorityQueue,它的大名叫做优先级队列,想必即使没有用过也该有所耳闻吧,什么?没..没听过?emmm... ...

  9. java读源码 之 list源码分析(LinkedList)

    文章目录 LinkedList: 继承关系分析: 字段分析: 构造函数分析: 方法分析: LinkedList: 继承关系分析: public class LinkedList<E> ex ...

随机推荐

  1. Building Applications with Force.com and VisualForce (DEV401) (四):Building Your user Interface

    Dev 401-004:Application essential:Building Your user Interface: Module Agenda1.Custom Applications2. ...

  2. 微信阻止ios下拉回弹,橡皮筋效果

    直接阻止touchmove事件就好了(需设置passive: false): document.addEventListener("touchmove", function(evt ...

  3. Unable to locate JAR/zip in file system as specified by the driver definitio

    把之前的驱动包删掉,然后把你的驱动包导入就行了 现在OK键就算正常了

  4. iOS 架构

    一.MVC MVC 全名 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离 ...

  5. sql MYSQL主从配置

    MYSQL主从配置 1.1 部署环境 主(master_mysql): 192.168.1.200 OS:CentOS 6.5 从(slave_mysql): 192.168.1.201 OS:Cen ...

  6. Supervisor 使用和进阶4 (Event 的使用)

    本文主要介绍 supervisor Event 的功能. supervisor 作为一个进程管理工具,在 3.0 版本之后,新增了 Event 的高级特性, 主要用于做(进程启动.退出.失败等)事件告 ...

  7. 2019NYIST计科第四次周赛

    YZJ的牛肉干 Description 今年的ACM暑期集训队一共有18人,分为6支队伍.其中有一个叫做 YZJ的大佬,在共同的集训生活中,大家建立了深厚的友谊, YZJ准备做点什么来纪念这段激情燃烧 ...

  8. XXE白盒审计 PHP

    XXE与XML注入的区别 https://www.cnblogs.com/websecurity-study/p/11348913.html XXE又分为内部实体和外部实体.我简单区分为内部实体就是自 ...

  9. 1000行MySQL学习笔记,不怕你不会,就怕你不学!

    Windows服务 -- 启动MySQL net start mysql-- 创建Windows服务 sc create mysql binPath= mysqld_bin_path(注意:等号与值之

  10. PTA数据结构与算法题目集(中文) 7-10

    PTA数据结构与算法题目集(中文)  7-10 7-10 公路村村通 (30 分)   现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低 ...