concurrent(五)同步辅助器CountDownLatch & 源码分析
参考文档:
https://blog.csdn.net/zxdfc/article/details/52752803
简介
CountDownLatch是一个同步辅助类。允许一个或多个线程等待其他线程完成操作。内部采用的公平锁和共享锁的机制实现
举个栗子
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch cd = new CountDownLatch(2);
ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 2; i++) {
es.execute(new MyThread1(cd));
}
System.out.println("wait");
try {
cd.await();
System.out.println("two thread run over");
} catch (InterruptedException e) {
e.printStackTrace();
}
es.shutdown();
}
}
class MyThread1 implements Runnable {
CountDownLatch latch;
public MyThread1(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(1000 * 3);
latch.countDown();
System.out.println(Thread.currentThread().getName() + " over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
源码分析


构造方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
函数列表
void await():如果当前count大于0,当前线程将会wait,直到count等于0或者中断。PS:当count等于0的时候,再去调用await(),线程将不会阻塞,而是立即运行。后面可以通过源码分析得到
boolean await(long timeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
void countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程
long getCount() :获得计数的数量
await分析
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判断是否发生中断
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//注意:-1表示还有线程未执行完毕,1表示等待的线程已经执行完毕
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//这里的state就是最开始new CountDownLatch(int count),count等于state
}
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);//就判断尝试获取锁
/*
这里要注意一下r的值就2种情况-1和1:
情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待
情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点
*/
if (r >= 0) { //1表示等待的线程已经执行完毕
setHeadAndPropagate(node, r);//将当前节点设置头结点,如果还有资源则唤醒后继节点
p.next = null; //删除原来的头结点的引用,则原头结点可以gc掉
failed = false;
return;
}
}
//如果当前线程不是第二个节点,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //调用park()使线程进入waiting状态,如果被唤醒,查看自己是不是被中断的
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 记录头结点
setHead(node);//设置node为头结点
/*
*从上面传递过来 propagate = 1;
*一定会进入下面的判断
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared
if (s == null || s.isShared())
doReleaseShared();//唤醒后继节点
}
}
//唤醒后继节点
private void doReleaseShared() {
for (;;) {
Node h = head;//获得头结点
if (h != null && h != tail) {
int ws = h.waitStatus;//获取头结点的状态默认值为0
if (ws == Node.SIGNAL) {//如果等于SIGNAL唤醒状态
//将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break
if (h == head) // loop if head changed
break;
}
}
countDown分析
public void countDown() {
sync.releaseShared(1);//每次释放一个
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//尝试释放共享锁
doReleaseShared();//释放共享锁,上面有泛型不在重复
//由于state等于0,所以从队列中,从头结点一个接着一个退出(break),for循环等待,
return true;
}
return false;
}
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))//比较并设置state
return nextc == 0; //等于0的时候,肯定还有一些Node在队列中,所以要调用doReleaseShared(),来清理
}
}
concurrent(五)同步辅助器CountDownLatch & 源码分析的更多相关文章
- concurrent(六)同步辅助器CyclicBarrier & 源码分析
参考文档:Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/3533995.html简介Cy ...
- Java - "JUC" CountDownLatch源码分析
Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...
- CountDownLatch 源码分析
CountDownLatch 源码分析: 1:CountDownLatch数据结构 成员变量 Sync类型对象 private final Sync sync; Sync是继承AQS的一个类,Coun ...
- JUC之CountDownLatch源码分析
CountDownLatch是AbstractQueuedSynchronizer中共享锁模式的一个的实现,是一个同步工具类,用来协调多个线程之间的同步.CountDownLatch能够使一个或多个线 ...
- 并发工具CountDownLatch源码分析
CountDownLatch的作用类似于Thread.join()方法,但比join()更加灵活.它可以等待多个线程(取决于实例化时声明的数量)都达到预期状态或者完成工作以后,通知其他正在等待的线程继 ...
- Mybatis Interceptor 拦截器原理 源码分析
Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...
- scrapy-redis(调度器Scheduler源码分析)
settings里面的配置:'''当下面配置了这个(scrapy-redis)时候,下面的调度器已经配置在scrapy-redis里面了'''##########连接配置######## REDIS_ ...
- 并发编程(五)——AbstractQueuedSynchronizer 之 ReentrantLock源码分析
本文将从 ReentrantLock 的公平锁源码出发,分析下 AbstractQueuedSynchronizer 这个类是怎么工作的,希望能给大家提供一些简单的帮助. AQS 结构 先来看看 AQ ...
- 第五章 类加载器ClassLoader源码解析
说明:了解ClassLoader前,先了解 第四章 类加载机制 1.ClassLoader作用 类加载流程的"加载"阶段是由类加载器完成的. 2.类加载器结构 结构:Bootstr ...
随机推荐
- 五、Hexo静态博客背景及界面显示优化配置
示例预览:我的主页 背景图片添加 自动切换背景 静态本地背景 首先将已选定的背景图片放到博客根目录下的\source\images下 示例:D:\Blog\source\images\backgr ...
- dotnet + LinQ 按照指定的字段 和 排序方式排序
/// <summary> /// 根据指定属性名称对序列进行排序 /// </summary> /// <typeparam name="TSource&qu ...
- 使用XSSFWorkbook 读取excel
工作中用到 使用XSSFWorkbook 读取excel 具体代码如下, private (List<(string columnName, string colomnDescription)& ...
- C# Newtonsoft.Json JsonSerializerSettings配置序列化操作
https://blog.csdn.net/u011127019/article/details/72801033
- Git恢复删除的分支
1.使用 git reflog 命令查看显示整个本地仓储的commit,包括所有branch的commit,甚至包括已经撤销的commit. 2.找到我们想要恢复的分支 ,可以看到我们当时commit ...
- “You don't have permission to access /phpmyadmin/ on this server.”
<Directory "I:/1/wamp/apps/phpmyadmin3.4.10.1/"> Options Indexes FollowSymLinks Mult ...
- sqlplus连接远程Oralce数据库
1. 下载 http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html 2. 三个包 in ...
- Jmeter 使用自定义变量
有些情况下比如发起测试时URL的主机名和端口需要在采样器中出现多次,这样就有个问题,当测试的主机更改时, 我们需要修改主机名称,这时就需要修改多个地方,如果多的情况会有遗漏.如果我们在配置脚本的时候, ...
- 解决Mac下SourceTree每次都让输入密码的问题
在Mac上操作sourcetree当pull和push时每次都是让输入密码,非常烦人,虽然大概知道是因为SSH什么的问题,但搜索百度也没发现解决办法. 于是乎搜索谷歌,发现如下解决办法. Source ...
- C语言形参跟实参详解
形参与实参今天我们来说下c语言形参与实参的区别,形参跟实参理解的话也很简单,但是好多同学关于这个知识点都是一知半解,没有真正的去透彻,一问都知道,但在真正引用的时候还会出现很多问题,而百度的时候又会说 ...