java并发计数器_浅谈java并发之计数器CountDownLatch
CountDownLatch简介
CountDownLatch顾名思义,count + down + latch = 计数 + 减 + 门闩(这么拆分也是便于记忆=_=) 可以理解这个东西就是个计数器,只能减不能加,同时它还有个门闩的作用,当计数器不为0时,门闩是锁着的;当计数器减到0时,门闩就打开了。
如果你感到懵比的话,可以类比考生考试交卷,考生交一份试卷,计数器就减一。直到考生都交了试卷(计数器为0),监考老师(一个或多个)才能离开考场。至于考生是否做完试卷,监考老师并不关注。只要都交了试卷,他就可以做接下来的工作了。
CountDownLatch实现原理
下面从构造方法开始,一步步解释实现的原理:构造方法下面是实现的源码,非常简短,主要是创建了一个Sync对象。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync对象
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;
}
}
}
假设我们是这样创建的:new CountDownLatch(5)。其实也就相当于new Sync(5),相当于setState(5)。setState其实就是共享锁资源总数,我们可以暂时理解为设置一个计数器,当前计数器初始值为5。
tryAcquireShared方法其实就是判断一下当前计数器的值,是否为0了,如果为0的话返回1(返回1的时候,就表示获取锁成功,awit()方法就不再阻塞)。
tryReleaseShared方法就是利用CAS的方式,对计数器进行减一的操作,而我们实际上每次调用countDownLatch.countDown()方法的时候,最终都会调到这个方法,对计数器进行减一操作,一直减到0为止。
countDownLatch.await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
代码很简单,就一句话(注意acquireSharedInterruptibly()方法是抽象类:AbstractQueuedSynchronizer的一个方法,我们上面提到的Sync继承了它),我们跟踪源码,继续往下看:
acquireSharedInterruptibly(int arg)
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
源码也是非常简单的,首先判断了一下,当前线程是否有被中断,如果没有的话,就调用tryAcquireShared(int acquires)方法,判断一下当前线程是否还需要“阻塞”。其实这里调用的tryAcquireShared方法,就是我们上面提到的java.util.concurrent.CountDownLatch.Sync.tryAcquireShared(int)这个方法。
当然,在一开始我们没有调用过countDownLatch.countDown()方法时,这里tryAcquireShared方法肯定是会返回-1的,因为会进入到doAcquireSharedInterruptibly方法。
doAcquireSharedInterruptibly(int arg)
countDown()方法
// 计数器减1
public void countDown() {
sync.releaseShared(1);
}
//调用AQS的releaseShared方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//计数器减一
doReleaseShared();//唤醒后继结点,这个时候队列中可能只有调用过await()的线程节点,也可能队列为空
return true;
}
return false;
}
这个时候,我们应该对于countDownLatch.await()方法是怎么“阻塞”当前线程的,已经非常明白了。其实说白了,就是当你调用了countDownLatch.await()方法后,你当前线程就会进入了一个死循环当中,在这个死循环里面,会不断的进行判断,通过调用tryAcquireShared方法,不断判断我们上面说的那个计数器,看看它的值是否为0了(为0的时候,其实就是我们调用了足够多 countDownLatch.countDown()方法的时候),如果是为0的话,tryAcquireShared就会返回1,代码也会进入到图中的红框部分,然后跳出了循环,也就不再“阻塞”当前线程了。
需要注意的是,说是在不停的循环,其实也并非在不停的执行for循环里面的内容,因为在后面调用parkAndCheckInterrupt()方法时,在这个方法里面是会调用 LockSupport.park(this);来挂起当前线程。
CountDownLatch 使用的注意点:
1、只有当count为0时,await之后的程序才够执行。
2、countDown必须写在finally中,防止发生异程常时,导致程序死锁。
使用场景:
比如对于马拉松比赛,进行排名计算,参赛者的排名,肯定是跑完比赛之后,进行计算得出的,翻译成Java识别的预发,就是N个线程执行操作,主线程等到N个子线程执行完毕之后,在继续往下执行。
public static void testCountDownLatch(){
int threadCount = 10;
final CountDownLatch latch = new CountDownLatch(threadCount);
for(int i=0; i< threadCount; i++){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getId() + "开始出发");
try {
Thread.sleep(1000);
System.out.println("线程" + Thread.currentThread().getId() + "已到达终点");
} catch (InterruptedException e) {
e.printStackTrace();
} fianlly {
latch.countDown();
}
}
}).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10个线程已经执行完毕!开始计算排名");
}
结果:
线程10开始出发
线程13开始出发
线程12开始出发
线程11开始出发
线程14开始出发
线程15开始出发
线程16开始出发
线程17开始出发
线程18开始出发
线程19开始出发
线程14已到达终点
线程15已到达终点
线程13已到达终点
线程12已到达终点
线程10已到达终点
线程11已到达终点
线程16已到达终点
线程17已到达终点
线程18已到达终点
线程19已到达终点
10个线程已经执行完毕!开始计算排名
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
java并发计数器_浅谈java并发之计数器CountDownLatch相关推荐
- java对象头_浅谈java对象结构 对象头 Markword
概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ----- ...
- java bitset用途_浅谈Java BitSet使用场景和代码示例
搜索热词 @H_502_0@一.什么是BitSet? @H_502_0@ 注:以下内容来自JDK API: @H_502_0@ BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个b ...
- java 多线程同步_浅谈Java多线程(状态、同步等)
Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容. 一.Java中线程创建的三种方式: 1.通过继承T ...
- java手动回收_浅谈java是如何做资源回收补救的
学习java的过程,我们经常谈论一个对象的回收,尤其是资源类型,如果没有显示的关闭,对象就被回收了,说明出现了资源泄漏.java本身为了防止这种情况,做了一些担保的方式,确保可以让未关闭的资源合理回收 ...
- java同名函数_浅谈Java 继承接口同名函数问题
在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); ...
- java 变量共享_浅谈Java共享变量
Java并发一直都是开发中比较难也比较有挑战性的技术,对于很多新手来说是很容易掉进这个并发陷阱的,其中尤以共享变量最具代表性,其实关于讲这个知识点网上也不少,但大象想讲讲自己对这个概念的理解.共享变量 ...
- java list翻转_浅谈Java数据结构中的常见问题
1.常用数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素间的关系组成.常用的数据有:数组.栈.队列.链表.树.图.堆.散列表. 1)数组:在内存中连续存储多个元素的 ...
- 00005在java结果输出_浅谈Java反序列化漏洞原理(案例未完善后续补充)
摘要: 0005,这个16进制流基本上也意味者java反序列化的开始:(2)HTTP:必有rO0AB,其实这就是aced0005的base64编码的结果:以上意味着存在Java反序列化,可尝试构造pa ...
- java反射 用处_浅谈Java反射
一.何为反射 反射就是对于任何一个类都能知道这个类的的所有属性和方法,并且对于任何一个对象都能调用他的属性和方法,而且能修改其属性. 二.反射的作用 就我的理解来看,通常我们在写代码的时会非常强调代码 ...
最新文章
- 大数据分布式集群搭建(9)
- Oracle 学习系列之一(表空间与表结构)
- 阶段-关口系统-stage2立项分析阶段---学习记录
- UA MATH523A 实分析1 集合论基础1 基本概念复习
- javascript:jquery.history.js使用方法
- 解决解决鼠标右键被锁定
- Android BUG调试相关方法整理
- lua绑定c++的时候常见得错误
- 【转】PPT精典基础教程
- ESXi7.0 安装 MacOS (ESXi Unlocker 3.0.3)
- 如何将IE11降级到IE10
- 毫米波雷达信号处理系统
- 机器学习十大算法!入门看这个就够了~
- SEMer需要知道的创意标题游戏规则
- COB,COF,COG区别
- Oracle Data Guard的三种保护模式
- 300iq Contest 1
- ubuntu WPS 提示“系统缺失字体symbol、wingdings、wingdings 2、wingdings 3、webding”的解决方法
- oracle 小数格式化为百分数
- 能够语义化的编写html文档,HTML语义化 - 写给未来的自己 - OSCHINA - 中文开源技术交流社区...
热门文章
- Server error for SAP CRM_BUPA_ODATA
- Axure external link打开原理
- how Machine Learning service is used in Jeans project
- S/4HANA生产订单的标准状态和透明工厂原型状态的映射
- 为什么C4C UI上看不到新建按钮
- One order text browser tool
- IBASE category 01 component hierarchy
- new Grammar in 740 - Internal table group by
- SAP Commerce的路由实现(Route Implementation)
- SAP Fiori Launchpad里home按钮的实现原理分析