Java CountDownLatch解析(下)
- 写在前面的话
在上一篇CountDownLatch解析中,我们了解了CountDownLatch的简介、CountDownLatch实用场景、CountDownLatch实现原理中的await()方法,
接下来我们接着来了解它的countDown()方法以及它的示例和优缺点。
- CountDownLatch实现原理
二、CountDownLatch.countDown()
关于同步队列那点事
当部分线程调用await()方法后,它们在同步队列中被挂起,然后自省的检查自己能否满足醒来的条件(还记得那个条件吗?1、state为0,2、该节点为头节点),
如果满足它将被移除头节点,并将下一个节点设置为头节点。具体如下图:
这个Node是AbstractQueuedSynchronizer(下文简称AQS)中的静态内部类,其中它又有两个属性:
volatile Node prev; volatile Node next;
volatile的prev指向上一个node节点,volatile的next指向下一个node节点。当然如果是头节点,那么它的prev为null,同理尾节点的next为null。
private transient volatile Node head; private transient volatile Node tail;
head和tail是AQS中的属性,它们用来表示同步队列的头节点和尾节点。(有兴趣的同学可以引申看一下 transident 这个关于序列化关键字,这里不展开啦=_=)
了解了同步队列后,接着我们开始线程调用countDown()方法后,到底发生了什么事?这儿咱们可以想象一下,可能计数值会减去1,而且还会判断state是不是等于0,
如果不等于0,这个线程就继续往下走了;如果等于0,那么它可能还需要去叫醒这群挂起的线程。
到底是不是这样呢,别方,跟着笔者一起继续来扒源码。
public void countDown() {
sync.releaseShared(1);
}
当调用CountDownLatch.countDown()后,它转而调用了Sync这个内部类实例的releaseShared()方法。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true; //退出该方法
}
return false; //退出该方法
}
在Sync类中并没有releaseShared()方法,所以应该是继承与AQS,咱们看到AQS这个方法中,退出该方法的只有两条路。tryReleaseShared(arg)条件为真执行一个doReleaseShared()退出;条件为假直接退出。
类比我们的猜测,这个条件很有可能就是state是否为0的判断。。。接着来看。
protected boolean tryReleaseShared(int releases) {
for (;;) {//死循环
int c = getState();// 获取主存中的state值
if (c == 0) //state已经为0 直接退出
return false;
int nextc = c-1; // 减一 准备cas更新该值
if (compareAndSetState(c, nextc)) //cas更新
return nextc == 0; //更新成功 判断是否为0 退出;更新失败则继续for循环,直到线程并发更新成功
}
}
看到这儿四不四灵光从脑子喷涌而出啦。我们的猜测是正确的!
private void doReleaseShared() {
for (;;) {//又是一个死循环
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {//如果当前节点是SIGNAL意味着,它正在等待一个信号,或者说它在等待被唤醒,因此做两件事,1是重置waitStatus标志位,2是重置成功后,唤醒下一个节点。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//如果本身头节点的waitStatus是出于重置状态(waitStatus==0)的,将其设置为“传播”状态。意味着需要将状态向后一个节点传播。
continue;
}
if (h == head)
break;
}
}
同学们(敲黑板...),我们为啥要执行这个方法呀,因为state已经为0啦,我们该将同步队列中的线程状态设置为共享状态(Node.PROPAGATE,默认状态ws == 0),并向后传播,实现状态共享。
这就是为啥方法名有个shared,因为这个共享状态需要传播下去,而不是一个节点(线程)独占。看看这个死循环,退出的路只有一条,那就是h==head,即该线程是头节点,且状态为共享状态。
这里再啰嗦一句,可能读者会问,state已经等于0了,我们也通过循环的方式把头节点的状态设置为共享状态,但是它怎么醒过来的呢?既然这样,那我们再看一次上一篇中的代码
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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;// 计数值为0 并且为头节点 跳出循环
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();// 响应打断 跳出循环
}
} finally {
if (failed)
cancelAcquire(node);// 如果是打断退出的 则移除同步队列节点
}
}
这个就是在同步队列中挂起的线程,它们自旋的形式查看自己是否满足条件醒来(state==0,且为头节点),如果成立将调用setHeadAndPropagate这个方法
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
这个方法是将当前节点的下一个节点设置为头节点,且它也调用了doReleaseShared这个方法,我们刚才也说了,这个方法就是将头节点设置为共享状态的,由此,共享状态传播下去。
至此,CountDownLatch实现原理中的countDown()方法剖析结束。
- CountDownLatch示例
废话不多说,直接上代码:
package com.test.demo; import java.util.concurrent.CountDownLatch; /**
* @Title: TestCountDownLatch.java
* @Describe:
* @author: Mr.Yanphet
* @Email: mr_yanphet@163.com
* @date: 2016年9月18日 上午11:22:42
* @version: 1.0
*/
public class TestCountDownLatch { private static final int taskNum = 5; static class MyRunnable implements Runnable { private int num; private CountDownLatch cdl; public MyRunnable(int num, CountDownLatch cdl){
this.num = num;
this.cdl = cdl;
} public void run() {
System.out.println("第" + num + "个线程开始执行任务...");
try {
Thread.sleep(5 * 1000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + num + "个线程任务执行结束...");
cdl.countDown();
}
} public static void main(String[] args) {
CountDownLatch cdl = new CountDownLatch(taskNum);
for (int i = 1; i <= taskNum; i++) {
MyRunnable mr = new MyRunnable(i, cdl);
Thread t = new Thread(mr);
t.start();
}
System.out.println("等待其他线程完成任务才继续执行...");
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他线程完成任务,主线程开始执行...");
System.out.println("主线程任务完成,整个任务进度完成...");
} }
执行结果如下:
第4个线程开始执行任务...
第5个线程开始执行任务...
第3个线程开始执行任务...
第2个线程开始执行任务...
第1个线程开始执行任务...
等待其他线程完成任务才继续执行...
第4个线程任务执行结束...
第2个线程任务执行结束...
第1个线程任务执行结束...
第3个线程任务执行结束...
第5个线程任务执行结束...
其他线程完成任务,主线程开始执行...
主线程任务完成,整个任务进度完成...
主线程等待5个子线程执行完任务,才继续往下执行自己的任务。
- CountDownLatch的优缺点
优点:
CountDownLatch的优点毋庸置疑,对使用者而言,你只需要传入一个int型变量控制任务数量即可,至于同步队列的出队入队维护,state变量值的维护对使用者都是透明的,使用方便。
缺点:
CountDownLatch设置了state后就不能更改,也不能循环使用。
以上就是关于CountDownLatch学习的全部内容。因为笔者也是菜鸟,所以站在菜鸟的角度分析源码,难免重复啰嗦。如有任何问题,希望大家指正。谢谢~~~
Java CountDownLatch解析(下)的更多相关文章
- Java CountDownLatch解析(上)
写在前面的话 最近一直在边工作边学习分布式的东西,看到了构建Java中间件的基础知识,里面有提到Java多线程并发的工具类,例如ReentrantLock.CyclicBarrier.CountDow ...
- java socket解析和发送二进制报文工具(附java和C++转化问题)
解析: 首先是读取字节: /** * 读取输入流中指定字节的长度 * <p/> * 输入流 * * @param length 指定长度 * @return 指定长度的字节数组 */ pu ...
- java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...
- java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别
java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- JAVA简便解析json文件
JAVA简便解析json文件 首先放上我要解析的json文件: { "resultcode":"200", "reason":"S ...
- XML概念定义以及如何定义xml文件编写约束条件java解析xml DTD XML Schema JAXP java xml解析 dom4j 解析 xpath dom sax
本文主要涉及:xml概念描述,xml的约束文件,dtd,xsd文件的定义使用,如何在xml中引用xsd文件,如何使用java解析xml,解析xml方式dom sax,dom4j解析xml文件 XML来 ...
- Java数据解析---JSON
一.Java数据解析分为:XML解析和JSON解析 XML解析即是对XML文件中的数据解析,而JSON解析即对规定形式的数据解析,比XML解析更加方便 JSON解析基于两种结构: 1.键值对类型 { ...
随机推荐
- LOJ#2882. 「JOISC 2014 Day4」两个人的星座(计算几何)
题面 传送门 题解 我们发现如果两个三角形相离,那么这两个三角形一定存在两条公切线 那么我们可以\(O(n^2)\)枚举其中一条公切线,然后可以暴力\(O(n^3)\)计算 怎么优化呢?我们可以枚举一 ...
- webpack 踩的坑
我是看着这篇博客学习的 http://www.jianshu.com/p/42e11515c10f# 看到loaders的时候,按照博主写法去试 结果报错....找了好久,上网查了好多 也看错误信息了 ...
- URL的三类编码格式(JavaScript实现)
编码函数: 1.escape(): 不编码的字符有69个:* + - . / @ _ 0~9 a~z A~Z 而且escape对0~255以外的Unicode值进行 ...
- KMP算法再解 (看毛片算法真是人如其名,哦不,法如其名。)
KMP算法主要解决字符串匹配问题,其中失配数组next很关键: 看毛片算法真是人如其名,哦不,法如其名. 看了这篇博客,转载过来看一波: 原博客地址:https://blog.csdn.net/sta ...
- jquery text html width heigth的用法
<body> <div id="div1"> <h3>我是标题</h3> </div> <div id=" ...
- jQuery 动画用法
jQuery动画: <head> <meta charset="UTF-8"> <title>Title</title> <s ...
- java运行时的内存区域
1.概述 java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线 ...
- Scrum Meeting 汇总
Alpha [Alpha]Scrum Meeting 0&1 [Alpha]Scrum Meeting 2 [Alpha]Scrum Meeting 3 [Alpha]Scrum Meetin ...
- 【实战】Axis2后台Getshell
实战遇到的情况---任意文件读取,读取/conf/axis2.xml内容,读取用户名和密码登录后台 当然弱口令也是屡试不爽的. 操作起来 1.上传cat.aar(链接:https://pan.baid ...
- dubbo接口快速测试技巧
在分布式系统的开发中,用到了dubbo+zookeeper技术,最近遇到一个问题,产品上线后,我负责的模块出了问题,某个bean中某个字段的值一直为null,而这个bean是我调用注册在zookeep ...