Java锁--CountDownLatch
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3533887.html
CountDownLatch简介
CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
CountDownLatch和CyclicBarrier的区别
(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
关于CyclicBarrier的原理,后面一章再来学习。
CountDownLatch函数列表

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

CountDownLatch数据结构
CountDownLatch的UML类图如下:

CountDownLatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。
CountDownLatch源码分析(基于JDK1.7.0_40)
CountDownLatch完整源码(基于JDK1.7.0_40)
CountDownLatch是通过“共享锁”实现的。下面,我们分析CountDownLatch中3个核心函数: CountDownLatch(int count), await(), countDown()。
1. CountDownLatch(int count)
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
说明:该函数是创建一个Sync对象,而Sync是继承于AQS类。Sync构造函数如下:
Sync(int count) {
setState(count);
}
setState()在AQS中实现,源码如下:
protected final void setState(long newState) {
state = newState;
}
说明:在AQS中,state是一个private volatile long类型的对象。对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。
2. await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
说明:该函数实际上是调用的AQS的acquireSharedInterruptibly(1);
AQS中的acquireSharedInterruptibly()的源码如下:

public final void acquireSharedInterruptibly(long arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

说明:acquireSharedInterruptibly()的作用是获取共享锁。
如果当前线程是中断状态,则抛出异常InterruptedException。否则,调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。
tryAcquireShared()在CountDownLatch.java中被重写,它的源码如下:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
说明:tryAcquireShared()的作用是尝试获取共享锁。
如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。

private void doAcquireSharedInterruptibly(long arg)
throws InterruptedException {
// 创建"当前线程"的Node节点,且Node中记录的锁是"共享锁"类型;并将该节点添加到CLH队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取上一个节点。
// 如果上一节点是CLH队列的表头,则"尝试获取共享锁"。
final Node p = node.predecessor();
if (p == head) {
long r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// (上一节点不是CLH队列的表头) 当前线程一直等待,直到获取到共享锁。
// 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

说明:
(01) addWaiter(Node.SHARED)的作用是,创建”当前线程“的Node节点,且Node中记录的锁的类型是”共享锁“(Node.SHARED);并将该节点添加到CLH队列末尾。关于Node和CLH在"Java多线程系列--“JUC锁”03之 公平锁(一)"已经详细介绍过,这里就不再重复说明了。
(02) node.predecessor()的作用是,获取上一个节点。如果上一节点是CLH队列的表头,则”尝试获取共享锁“。
(03) shouldParkAfterFailedAcquire()的作用和它的名称一样,如果在尝试获取锁失败之后,线程应该等待,则返回true;否则,返回false。
(04) 当shouldParkAfterFailedAcquire()返回ture时,则调用parkAndCheckInterrupt(),当前线程会进入等待状态,直到获取到共享锁才继续运行。
doAcquireSharedInterruptibly()中的shouldParkAfterFailedAcquire(), parkAndCheckInterrupt等函数在"Java多线程系列--“JUC锁”03之 公平锁(一)"中介绍过,这里也就不再详细说明了。
3. countDown()
public void countDown() {
sync.releaseShared(1);
}
说明:该函数实际上调用releaseShared(1)释放共享锁。
releaseShared()在AQS中实现,源码如下:

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。
它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。
tryReleaseShared()在CountDownLatch.java中被重写,源码如下:

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;
}
}

说明:tryReleaseShared()的作用是释放共享锁,将“锁计数器”的值-1。
总结:CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!
以上,就是CountDownLatch的实现原理。
CountDownLatch的使用示例
下面通过CountDownLatch实现:"主线程"等待"5个子线程"全部都完成"指定的工作(休眠1000ms)"之后,再继续运行。

1 import java.util.concurrent.CountDownLatch;
2 import java.util.concurrent.CyclicBarrier;
3
4 public class CountDownLatchTest1 {
5
6 private static int LATCH_SIZE = 5;
7 private static CountDownLatch doneSignal;
8 public static void main(String[] args) {
9
10 try {
11 doneSignal = new CountDownLatch(LATCH_SIZE);
12
13 // 新建5个任务
14 for(int i=0; i<LATCH_SIZE; i++)
15 new InnerThread().start();
16
17 System.out.println("main await begin.");
18 // "主线程"等待线程池中5个任务的完成
19 doneSignal.await();
20
21 System.out.println("main await finished.");
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 }
26
27 static class InnerThread extends Thread{
28 public void run() {
29 try {
30 Thread.sleep(1000);
31 System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
32 // 将CountDownLatch的数值减1
33 doneSignal.countDown();
34 } catch (InterruptedException e) {
35 e.printStackTrace();
36 }
37 }
38 }
39 }

运行结果:

main await begin.
Thread-0 sleep 1000ms.
Thread-2 sleep 1000ms.
Thread-1 sleep 1000ms.
Thread-4 sleep 1000ms.
Thread-3 sleep 1000ms.
main await finished.

结果说明:主线程通过doneSignal.await()等待其它线程将doneSignal递减至0。其它的5个InnerThread线程,每一个都通过doneSignal.countDown()将doneSignal的值减1;当doneSignal为0时,main被唤醒后继续执行。
Java锁--CountDownLatch的更多相关文章
- 转 : 深入解析Java锁机制
深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...
- Java中CountDownLatch类的使用
0.CountDownLatch作用 1) Java api中的解释:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 2) CountDownLatch可以使J ...
- Java锁-Synchronized深层剖析
Java锁-Synchronized深层剖析 前言 Java锁的问题,可以说是每个JavaCoder绕不开的一道坎.如果只是粗浅地了解Synchronized等锁的简单应用,那么就没什么谈的了,也不建 ...
- Java并发(九)【转载】不可不说的Java“锁”事
转载自 美团技术团队,原文链接 不可不说的Java“锁”事 前言 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率.本文旨在对锁相关源码(本文中的源码来自JDK ...
- Java锁--公平锁
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3496147.html 基本概念 本章,我们会讲解“线程获取公平锁”的原理:在讲解之前,需要了解几个基本概 ...
- Java锁--框架
根据锁的添加到Java中的时间,Java中的锁,可以分为"同步锁"和"JUC包中的锁". 同步锁 即通过synchronized关键字来进行同步,实现对竞争资源 ...
- 25.Java锁的深度化
Java锁的深度化 悲观锁.乐观锁.排他锁 场景 当多个请求同时操作数据库时,首先将订单状态改为已支付,在金额加上200,在同时并发场景查询条件下,会造成重复通知. SQL: Update 悲观锁与乐 ...
- java 锁 简介(转)
转自 https://www.cnblogs.com/hustzzl/p/9343797.html 1. Java锁的种类 在笔者面试过程时,经常会被问到各种各样的锁,如乐观锁.读写锁等等,非常繁多, ...
- 硬核剖析Java锁底层AQS源码,深入理解底层架构设计
我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...
随机推荐
- Django之用户注册
用户注册需要提交的信息包括: 用户名 邮箱 密码 确认密码 验证码 这里选择form表单提交信息,注册页面的响应函数就要分条件执行,get请求时要展示注册页面,post请求时要接收用户提交的信息,对信 ...
- Mstar方案软件运行基本原理
1. MApp_Main.c里有个while(1)循环: 2. 通过 while(1)循环MApp_MultiTasks 里面的 MApp_ProcessUserInput 可以 得到 当前的 u8K ...
- 用Nacos做微服务架构里的服务注册与发现中心
转自:https://www.jianshu.com/p/61608ff86344 Nacos 另一个非常重要的特性就是服务注册与发现,说到服务的注册与发现相信大家应该都不陌生,在微服务盛行的今天,服 ...
- 022 Android .9图片的含义及制作教程
1.图片(.9.png格式)的概念 (1)9patch图片是andriod app开发里一种特殊的图片形式,文件的扩展名为:.9.png (2)9patch图片的作用就是在图片拉伸的时候保证其不会失真 ...
- 09 IO流(六)——ByteArray字节流、流对接
字节数组流 ByteArray流是节点流. 前面讲到的文件字节流,文件字符流,他们的源都是来自于pc硬盘. 本节要讲的字节数组流的源是来自于内存或网络. 它更合适用来处理来自网络的字节流. 由于源并不 ...
- 20 闭包、nonlocal
闭包的概念 闭包就是能够读取其他函数内部变量的函数. 从模块级别调用函数内部的局部变量. 闭包 = 函数+环境变量(函数外部的变量) 闭包存在的条件 闭包必须返回一个函数 被返回的函数必须调用环境变量 ...
- Arm-Linux 移植 jpeg库
背景: jpeg库的使用可以提高显示效率. host平台 :Ubuntu 16.04 arm平台 : S5P6818 jpeg :v9c arm-gcc :4.8.1 主机准备: 运行以下脚 ...
- 织梦安全防护:禁止uploads、data、templets执行脚本
下面介绍下如何针对uploads.data.templets做PHP脚本限制:对uploads.data.templets 三个目录做执行php脚本限制,就算被上传了木马文件到这些文件夹,也是无法运行 ...
- [Luogu5319][BJOI2019]奥术神杖(分数规划+AC自动机)
对最终答案取对数,得到$\ln(Ans)=\frac{1}{c}\sum \ln(v_i)$,典型的分数规划问题.二分答案后,对所有咒语串建立AC自动机,然后套路地$f[i][j]$表示走到T的第i个 ...
- L2范数归一化概念和优势
1 归一化处理 归一化是一种数理统计中常用的数据预处理手段,在机器学习中归一化通常将数据向量每个维度的数据映射到(0,1)或(-1,1)之间的区间或者将数据向量的某个范数映射为1,归一化 ...