CountDownLatch 是一个同步工具类,允许一个线程或者多个线程等待其他线程完成操作,再执行。

CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。 // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()

CountDownLatch和CyclicBarrier的区别:

(1).CountDownLatch 的作用是允许1或者多个线程,等待另外N个线程完成某件事情之后,这1个或者多个线程才能执行。CyclicBarrier 是N个线程相互等待,任何一个线程完成任务之前,所有的线程必须等待。

(2).CountDownLatch 计数器是一次性的,无法被重置的,而CyclicBarrier的计数器在调用reset方法之后,还可以重新使用,因此被称为循环的barrier。

CountDownLatch 底层实现:

1.构造方法:创建一个Sync对象,而Sync继承AQS。

 /**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

2.Sync 是CountDownLatch的内部私有类,组合到CountDownLatch里:

 /**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count);
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} 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))
return nextc == 0;
}
}
} private final Sync sync;

在AQS中state是一个private volatile int类型的对象。CountDownLatch使用state来计数,CountDownLatch的getCount最终调用的是AQS的getState()

,返回state进行计数。

3.await()方法:调用AQS的acquireSharedInterruptibly方法

  public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
 //1.获取共享锁
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//判断线程是否为中断状态,如果是抛出interruptedException
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取共享锁,尝试成功就返回,否则调用doAcquireSharedInterruptibly方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//2.尝试获取共享锁,重写AQS里面的方法
protected int tryAcquireShared(int acquires) {
//锁状态 == 0,表示所没有被任何线程所获取,即是可获取的状态,否则锁是不可获取的状态
return (getState() == 0) ? 1 : -1;
}
//3.doAcquireSharedInterruptibly方法会使得当前线程一直等待,直到当前线程获取到锁(或被中断)才返回
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//创建“当前线程”的Node节点,且node中记录的锁是“共享锁”类型,并将节点添加到CLH队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取前继节点,如果前继节点是等待锁队列的表头,则尝试获取共享锁
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//前继节点不是表头,当前线程一直等待,直到获取到锁
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
 /*说明:4.shouldParkAfterFailedAcquire 返回当前线程是否应该阻塞
(01) 关于waitStatus请参考下表(中扩号内为waitStatus的值),更多关于waitStatus的内容,可以参考前面的Node类的介绍。 CANCELLED[1] -- 当前线程已被取消
SIGNAL[-1] -- “当前线程的后继线程需要被unpark(唤醒)”。一般发生情况是:当前线程的后继线程处于阻塞状态,而当前线程被release或cancel掉,因此需要唤醒当前线程的后继线程。
CONDITION[-2] -- 当前线程(处在Condition休眠状态)在等待Condition唤醒
PROPAGATE[-3] -- (共享锁)其它线程获取到“共享锁”
[0] -- 当前线程不属于上面的任何一种状态。
(02) shouldParkAfterFailedAcquire()通过以下规则,判断“当前线程”是否需要被阻塞。 规则1:如果前继节点状态为SIGNAL,表明当前节点需要被unpark(唤醒),此时则返回true。
规则2:如果前继节点状态为CANCELLED(ws>0),说明前继节点已经被取消,则通过先前回溯找到一个有效(非CANCELLED状态)的节点,并返回false。
规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,并返回false。
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前驱节点的状态
int ws = pred.waitStatus;
// 如果前驱节点是SIGNAL状态,则意味着当前线程需要unpark唤醒,此时返回true
if (ws == Node.SIGNAL) return true;
// 如果前继节点是取消的状态,则设置当前节点的“当前前继节点为”原节点的前继节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// waitStatus must be 0 or PROPAGATE. Indicate that we need a signal, but don't park yet. Caller will need to retry to make sure
//it cannot acquire before parking.
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

4. countDown()源码 :

//1.该方法其实调用AQS中的releaseShared(1)释放共享锁方法。
public void countDown() {
sync.releaseShared(1);
}
//2.目的是让当前线程释放它所持有的共享锁,它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//3.tryReleaseShared()在CountDownLatch.java中被重写,释放共享锁,将锁计数器-1
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
// 获取“锁计数器”的状态
int c = getState();
if (c == 0)
return false;
// “锁计数器”-1
int nextc = c-1;
// 通过CAS函数进行赋值。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

实例:

public class CountDownLatchTest1 {
private static int SPORTSMAN_COUNT = 10;
private static final Random random = new Random();
// 用于判断发令之前运动员是否已经进入准备状态,需要等待10个运动员准备就绪,占有锁,等待10个运动员完成,释放锁。
private static CountDownLatch readyLatch = new CountDownLatch(SPORTSMAN_COUNT);
// 用于判断裁判是否已经发令,占有锁,等待裁判发令完成,释放锁
private static CountDownLatch startLatch = new CountDownLatch(1); public static void main(String[] args) { // 用于判断发令之前运动员是否已经进入准备状态,需要等待10个运动员准备就绪,占有锁,等待10个运动员完成,释放锁。
// CountDownLatch readyLatch = new CountDownLatch(SPORTSMAN_COUNT);
// 用于判断裁判是否已经发令,占有锁,等待裁判发令完成,释放锁
// CountDownLatch startLatch = new CountDownLatch(1); // 启动10个线程,也就是10个运动员,做准备工作
for (int i = 0; i < SPORTSMAN_COUNT; i++) {
Thread t = new Thread(new MyTask((i + 1) + "号运动员", readyLatch, startLatch));
t.start();
}
// 当前运动员在其他运动员准备就绪前一直等待,也就是说等readyLatch倒数计数器为0之前一直等待
try {
readyLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} // 裁判发令,释放锁
startLatch.countDown(); System.out.println("裁判:所有运动员准备完毕,开始..."); } static class MyTask implements Runnable { private Lock lock = new ReentrantLock(); private CountDownLatch ready;
private CountDownLatch start;
private String name; /**
*
* (构造方法)
*
* @param ready
* @param start
* @param name 运动员名称
*/
public MyTask(String name, CountDownLatch ready, CountDownLatch start) {
this.ready = ready;
this.start = start;
this.name = name;
} @Override
public void run() {
lock.lock();
try { // 1. 写运动员准备就绪的逻辑,准备readyTime秒
int readyTime = random.nextInt(1000);
System.out.println(name + ":我需要" + readyTime + "秒的时间准备。");
try {
Thread.sleep(readyTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "我已经准备完毕!");
// 释放锁readyLatch-1,表示一个运动员已经就绪
ready.countDown();
try {
// 等待裁判发开始命令
start.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":开跑...");
} catch (Exception e) {
// TODO: handle exception
} finally {
lock.unlock();
} } } }

运行结果:

1号运动员:我需要757秒的时间准备。
2号运动员:我需要9秒的时间准备。
3号运动员:我需要602秒的时间准备。
4号运动员:我需要232秒的时间准备。
5号运动员:我需要454秒的时间准备。
6号运动员:我需要440秒的时间准备。
7号运动员:我需要333秒的时间准备。
8号运动员:我需要406秒的时间准备。
9号运动员:我需要613秒的时间准备。
10号运动员:我需要121秒的时间准备。
2号运动员我已经准备完毕!
10号运动员我已经准备完毕!
4号运动员我已经准备完毕!
7号运动员我已经准备完毕!
8号运动员我已经准备完毕!
6号运动员我已经准备完毕!
5号运动员我已经准备完毕!
3号运动员我已经准备完毕!
9号运动员我已经准备完毕!
1号运动员我已经准备完毕!
裁判:所有运动员准备完毕,开始...
10号运动员:开跑...
8号运动员:开跑...
3号运动员:开跑...
1号运动员:开跑...
2号运动员:开跑...
9号运动员:开跑...
5号运动员:开跑...
6号运动员:开跑...
7号运动员:开跑...
4号运动员:开跑...

总结:CountDownLatch通过AQS里面的共享锁来实现的,在创建CountDownLatch时候,会传递一个参数count,该参数是锁计数器的初始状态,表示该共享锁能够被count个线程同时获取。当某个线程调用CountDownLatch对象的await方法时候,该线程会等待共享锁可获取时,才能获取共享锁继续运行,而共享锁可获取的的条件是state == 0,而锁倒数计数器的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时候,计数器才-1,所以必须有count个线程调用该countDown()方法后,锁计数器才为0,这个时候等待的线程才能继续运行。




JUC系列回顾之-CountDownLatch底层原理和示例的更多相关文章

  1. 并发编程JUC系列AQS(CountDownLatch、CyclicBarrier、Semaphore)

    一.CountDownLatch package com.jonychen.test; import java.util.concurrent.CountDownLatch; import java. ...

  2. Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)

    刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问 上一篇文章我们获取到了DefaultSqlSe ...

  3. JUC回顾之-CyclicBarrier底层实现和原理

    1.CyclicBarrier 字面意思是可循环(Cyclic)使用的屏障(Barrier).它要做的事情是让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时候,屏障才会开门.所有被 ...

  4. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

    概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...

  5. Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

    概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 ...

  6. Java并发编程系列-(8) JMM和底层实现原理

    8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...

  7. (前篇:NIO系列 推荐阅读) Java NIO 底层原理

    出处: Java NIO 底层原理 目录 1.1. Java IO读写原理 1.1.1. 内核缓冲与进程缓冲区 1.1.2. java IO读写的底层流程 1.2. 四种主要的IO模型 1.3. 同步 ...

  8. 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟

    1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...

  9. 抛开 Spring ,你知道 MyBatis 加载 Mapper 的底层原理吗?

    原文链接:抛开 Spring ,你知道 MyBatis 加载 Mapper 的底层原理吗? 大家都知道,利用 Spring 整合 MyBatis,我们可以直接利用 @MapperScan 注解或者 @ ...

随机推荐

  1. json(2)

    JSON 语法规则 JSON 语法是 JavaScript 对象表示法语法的子集. 数据在名称/值对中 数据由逗号分隔 花括号保存对象 方括号保存数组 JSON 名称/值对 JSON 数据的书写格式是 ...

  2. ecshop后台"云提醒未激活 点击激活" 补丁删除方法

    ecshop后台"云提醒未激活 点击激活" 补丁删除方法 ECSHOP教程/ ecshop教程网(www.ecshop119.com) 2015-01-15   ecshop后台提 ...

  3. Protocol Buffer技术详解(Java实例)

    Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...

  4. Mac Sublime Text 2 简单使用

    按 Ctrl+` 调出 console 粘贴以下代码到底部命令行并回车: import urllib2,os;pf='Package Control.sublime-package';ipp=subl ...

  5. java中获取路径中的空格处理(%20)问题

    在java中获取文件路径的时候,有时候会获取到空格,但是在中文编码环境下,空格会变成“%20”从而使得路径错误. 解决办法: String path = Parameter.class.getReso ...

  6. 【转】websocket协议规范

    在线版目录: 1.引言——WebSocket协议翻译 2.一致性要求——WebSocket协议翻译 3.WebSocket URI——WebSocket协议翻译 4.打开阶段握手——WebSocket ...

  7. SSN 社会安全号码

    SSN是 Social Security Number 的缩写,译为「社会安全号码」,为美国社会安全卡(Social Security Card)上的 9 位数字.SSN 原本目的是用于追踪个人的纳税 ...

  8. JavaScript的Eval与JSON.parse的区别

    JavaScript的Eval与JSON.parse的区别 json的定义以及用法: JSON(JavaScript Object Notation)是一种轻量级的数据格式,采用完全独立于语言的文本格 ...

  9. javascript常量

    javascript中没有常量的概念,虽然许多现代的变成环境可能为您提供了用以创建常量的const语句.对于的自己的变量,可以采用相同的命名约定,并且将他们以静态属性的方式添加到构造函数中. //构造 ...

  10. 漫长Appium之路(二)——Appium安装与使用总结

    前面介绍了iOS自动化工具的Appium所需的虚拟机环境,接下来介绍下Appium的安装与使用方法,这个足足折腾我将近一个星期.网上没有什么详细的资料,对于遇到的各种各样问题也没用提供明确的解决方法. ...