【Java并发编程实战】-----“J.U.C”:CountDownlatch
上篇博文(【Java并发编程实战】-----“J.U.C”:CyclicBarrier)LZ介绍了CyclicBarrier。CyclicBarrier所描述的是“允许一组线程互相等待,直到到达某个公共屏障点,才会进行后续任务”。而CountDownlatch和它也有一点点相似之处:CountDownlatch所描述的是“在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待”。在JDK API中是这样阐述的:
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
虽然,CountDownlatch与CyclicBarrier有那么点相似,但是他们还是存在一些区别的:
1、CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
2、 CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
CountDownLatch分析
CountDownLatch结构如下:
从上图中可以看出CountDownLatch依赖Sync,其实CountDownLatch内部采用的是共享锁来实现的(内部Sync的实现可以看出)。它的构造函数如下:
CountDownLatch(int count):构造一个用给定计数初始化的 CountDownLatch。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
以下源代码可以证明,CountDownLatch内部是采用共享锁来实现的:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; protected int tryAcquireShared(int acquires) {
/** 省略源代码 **/
} protected boolean tryReleaseShared(int releases) {
/** 省略源代码 **/
}
}
CountDownLatch提供了await方法来实现:
await():使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
await(long timeout, TimeUnit unit): 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
await内部调用sync的acquireSharedInterruptibly方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//线程中断,抛出InterruptedException异常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
acquireSharedInterruptibly()的作用是获取共享锁。如果在获取共享锁过程中线程中断则抛出InterruptedException异常。否则通过tryAcquireShared方法来尝试获取共享锁。如果成功直接返回,否则调用doAcquireSharedInterruptibly方法。
tryAcquireShared源码:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
tryAcquireShared方法被CountDownLatch重写,他的主要作用是尝试着获取锁。getState == 0 表示锁处于可获取状态返回1否则返回-1;当tryAcquireShared返回-1获取锁失败,调用doAcquireSharedInterruptibly获取锁:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//创建当前线程(共享锁)Node节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取当前节点的前继节点
final Node p = node.predecessor();
//如果当前节点为CLH列头,则尝试获取锁
if (p == head) {
//获取锁
int 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);
}
}
该方法当中的方法,前面博客都讲述过,请参考:【Java并发编程实战】-----“J.U.C”:ReentrantLock之二lock方法分析、【Java并发编程实战】-----“J.U.C”:Semaphore。
CountDownLatch,除了提供await方法外,还提供了countDown(),countDown所描述的是“递减锁存器的计数,如果计数到达零,则释放所有等待的线程。”,源码如下:
public void countDown() {
sync.releaseShared(1);
}
countDown内部调用releaseShared方法来释放线程:
public final boolean releaseShared(int arg) {
//尝试释放线程,如果释放释放则调用doReleaseShared()
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared,同时被CountDownLatch重写了:
protected boolean tryReleaseShared(int releases) {
for (;;) {
//获取锁状态
int c = getState();
//c == 0 直接返回,释放锁成功
if (c == 0)
return false;
//计算新“锁计数器”
int nextc = c-1;
//更新锁状态(计数器)
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
总结:
CountDownLatch内部通过“共享锁”实现。在创建CountDownLatch时,需要传递一个int类型的count参数,该count参数为“锁状态”的初始值,该值表示着该“共享锁”可以同时被多少线程获取。当某个线程调用await方法时,首先判断锁的状态是否处于可获取状态(其条件就是count==0?),如果共享锁可获取则获取共享锁,否则一直处于等待直到获取为止。当线程调用countDown方法时,计数器count – 1。当在创建CountDownLatch时初始化的count参数,必须要有count线程调用countDown方法才会使计数器count等于0,锁才会释放,前面等待的线程才会继续运行。
实例
员工开会只有当所有人到期之后才会开户。我们初始化与会人员为3个,那么CountDownLatch的count应为3:
public class Conference implements Runnable{
private final CountDownLatch countDown; public Conference(int count){
countDown = new CountDownLatch(count);
} /**
* 与会人员到达,调用arrive方法,到达一个CountDownLatch调用countDown方法,锁计数器-1
* @author:chenssy
* @data:2015年9月6日
*
* @param name
*/
public void arrive(String name){
System.out.println(name + "到达.....");
//调用countDown()锁计数器 - 1
countDown.countDown();
System.out.println("还有 " + countDown.getCount() + "没有到达...");
} @Override
public void run() {
System.out.println("准备开会,参加会议人员总数为:" + countDown.getCount());
//调用await()等待所有的与会人员到达
try {
countDown.await();
} catch (InterruptedException e) {
}
System.out.println("所有人员已经到达,会议开始.....");
}
}
参加与会人员Participater:
public class Participater implements Runnable{
private String name;
private Conference conference; public Participater(String name,Conference conference){
this.name = name;
this.conference = conference;
} @Override
public void run() {
conference.arrive(name);
}
}
Test:
public class Test {
public static void main(String[] args) {
//启动会议室线程,等待与会人员参加会议
Conference conference = new Conference(3);
new Thread(conference).start(); for(int i = 0 ; i < 3 ; i++){
Participater participater = new Participater("chenssy-0" + i , conference);
Thread thread = new Thread(participater);
thread.start();
}
}
}
运行结果:
准备开会,参加会议人员总数为:3
chenssy-01到达.....
还有 2没有到达...
chenssy-00到达.....
还有 1没有到达...
chenssy-02到达.....
还有 0没有到达...
所有人员已经到达,会议开始.....
【Java并发编程实战】-----“J.U.C”:CountDownlatch的更多相关文章
- 【JAVA并发编程实战】4、CountDownLatch
这是一个计数锁,说白了,就是当你上锁的时候,只有计数减少到0的时候,才会释放锁 package cn.xf.cp.ch05; public class TaskRunable implements R ...
- 【Java并发编程实战】-----“J.U.C”:CyclicBarrier
在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock
ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...
- 【Java并发编程实战】-----“J.U.C”:Semaphore
信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析
前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介
注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
- 【Java并发编程实战】----- AQS(二):获取锁、释放锁
上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...
- 【Java并发编程实战】—– AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...
随机推荐
- 07.LoT.UI 前后台通用框架分解系列之——强大的文本编辑器
LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...
- Dapper逆天入门~强类型,动态类型,多映射,多返回值,增删改查+存储过程+事物案例演示
Dapper的牛逼就不扯蛋了,答应群友做个入门Demo的,现有园友需要,那么公开分享一下: 完整Demo:http://pan.baidu.com/s/1i3TcEzj 注 意 事 项:http:// ...
- Power BI官方视频(3) Power BI Desktop 8月份更新功能概述
Power BI Desktop 8月24日发布了更新版本.现将更新内容翻译整理如下,可以根据后面提供的链接下载最新版本使用. 1.主要功能更新 1.1 数据钻取支持在线版 以前的desktop中进行 ...
- pt-pmp
pt-pmp有两方面的作用:一是获取进程的堆栈信息,二是对这些堆栈信息进行汇总. 进程的堆栈信息是利用gdb获取的,所以在获取的过程中,会对mysql服务端的性能有一定的影响. 用官方的话说: Thi ...
- 体验报告:微信小程序在安卓机和苹果机上的区别
很多人可能会问:微信小程序和在微信里面浏览一个网页有什么区别? 首先,小程序的运行是全屏的,界面跟进入了一个APP很像,更为沉浸跟在微信里面访问h5不一样:其次,它的浏览体验更为稳定. 不过,这还不够 ...
- Linux根文件系统分析之init和busybox
Hi,大家好!我是CrazyCatJack.今天给大家讲解Linux根文件系统的init进程和busybox的配置及编译. 先简单介绍一下,作为一个嵌入式系统,要想在硬件上正常使用的话.它的软件组成大 ...
- ABP理论之CSRF
返回总目录 本篇目录 介绍 ASP.NET MVC ASP.NET WEB API ASP.NET Core[以后补上] 客户端类库 内部原理 介绍 CSRF[Cross-Site Request F ...
- 安全防范:nginx下git引发的隐私泄露问题
安全防范:nginx下git引发的隐私泄露问题 1 安全事件 最近阿里云服务器后台管理系统中收到一条安全提示消息,系统配置信息泄露: http://my.domain.com/.git/confi ...
- 小谈Scrum敏捷开发流程
一晃眼,有两年没有写博客了,回顾前两年,各种奔波,各种忙碌,也有不少的收获.从今天开始,我要把这些收获都分享在这里. 其实这两年,对我影响最大的是开发流程.总所周知,一个好的开发流程,对于项目的进行, ...
- iOS 开发不可缺少的工具
1.截屏利器:Snip 强大的滚动截屏功能,你值得拥有! Snip.png 传送门:http://www.snip.qq.com/ 2.Mac上最好的终端模拟器:iTerm2 iTeam.png 传送 ...