文章篇幅较短,对于一些AQS的顶级方法例如releaseShared并没有做过深的讲解,因为这些算是AQS的范畴,关于AQS可以看下另一篇文章——AQS

CountDownLatch一般被称作"计数器",作用大致就是数量达到了某个点之后计数结束,才能继续往下走。可以用作流程控制之类的作用,大流程分成多个子流程,然后大流程在子流程全部结束之前不动(子流程最好是相互独立的,除非能很好的控制两个流程的关联关系),子流程全部结束后大流程开始操作。

 很抽象,小问题,下方的两节或许能让你理解CountDownLatch的用法和内部的实现。

1.CountDownLatch的使用

  假设现在,我们要起一个3块钱的集资项目,并且限定每个人一次只能捐1块钱当募集到3块钱的时候立马就把这笔钱捐给我自己,如果凑齐之后你还想捐,那么我会跟你说,项目已经完成了,你这一块钱我不受理,自己去买雪糕吃吧;如果没凑齐,那么我这个募集箱就一直挂在这里。这个场景用CountDownLatch可以很契合的模拟出来。

  字数也不凑了,直接看demo例子吧

public static void main(String[] args) throws InterruptedException {

    // 集资项目==========>启动,目标3块钱
CountDownLatch countDownLatch = new CountDownLatch(3);
ThreadPoolExecutor executor = ThreadPoolProvider.getInstance();
executor.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.err.println("张1准备捐一块钱");
countDownLatch.countDown();
System.err.println("张1捐了一块钱");
} catch (InterruptedException e) {
e.printStackTrace();
} }); executor.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.err.println("张2准备捐一块钱");
countDownLatch.countDown();
System.err.println("张2捐了一块钱");
} catch (InterruptedException e) {
e.printStackTrace();
}
}); executor.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.err.println("张3准备捐一块钱");
countDownLatch.countDown();
System.err.println("张3捐了一块钱");
} catch (InterruptedException e) {
e.printStackTrace();
}
}); System.err.println("我项目启动后,就在这里等人捐钱,不够3块我不走了");
countDownLatch.await();
System.err.println("3块钱到手,直接跑路"); executor.shutdown();
}

结果图:



这个结果,em,可以看到countDownLatch使用的几个注意点:

  1. 调用countDownLatchawait()方法的线程会阻塞,直到凑够3块钱为止
  2. CyclicBarrier不同,其计完数之后并不会阻塞,而是直接执行接下来的操作
  3. 每次调用countDown()方法都会捐一块钱(计数一次),满了之后调用await()方法的线程不再阻塞

 另外,在上面的代码中,在countDown方法之后还打印信息是为了验证countDown方法不会阻塞当前线程,执行结果不一定如上图那样有顺序的,例如可能出现下方的结果:



 因为最后一个countDown之后,await所在的线程不再阻塞了,又正好赶上JVM线程调度,所以就会出现上方的结果。

2.CountDownLatch的内部实现

  刚才已经讲了CountDownLatch的用法,用起来还是不难的。那来看下内部是怎么实现的,又是怎么做到计数之后不跟CyclicBarrier一样阻塞的呢?

  首先来看构造函数吧,CountDownLatch只有一个构造函数,如下

public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

 所做的事情也就只有初始化内部对象sync一件事情(校验总不能算一件事吧?),那来看下初始化了个啥玩意

// 变量sync,是不是看起来很眼熟?
private final Sync sync; // 内部类Sync,又是一个AQS的产物
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; // 构造方法,就是设置了AQS的state值
Sync(int count) {
setState(count);
} int getCount() {
return getState();
} /*
* 可以知道countDownLatch使用的是AQS的共享模式
* 获取资源方法,正数表示成功,负数表示失败
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} // 释放方法
protected boolean tryReleaseShared(int releases) {
for (;;) {
// state的状态,在countDownLatch中表示剩余计数量,如果为0则表示可以被获取,即await方法不再阻塞
int c = getState();
if (c == 0)
return false;
// 本次计数后还剩余的及数量
int nextc = c-1;
// CAS设置剩余计数量
if (compareAndSetState(c, nextc))
// ==0表示锁释放了,之后state的值将一直是0,意思就是之后的await方法都不再阻塞
return nextc == 0;
}
}

 既然涉及到了AQS,那你应该懂我意思了——快去看我写的AQS文章啊

 开个玩笑,我知道各位都多多少少了解一些,上方代码的作用应该知道是干嘛的,不懂也没关系,等下我在下面再讲。

 回到正题,来讲下从上方代码能得到什么信息

1.CountDownLatch构造函数count的参数作用就是设置其内部的AQS的状态state,假设count3,那么每次进行countDownAQSstate就减1,减到0的时候await方法就不再阻塞,注意这时候await方法就不再阻塞了,无论你调多少次。

2.CountDownLatch里边的Sync实现的AQS的共享模式(从tryReleaseShared方法可以看出)

 到这里对其CountDownLatch的内部有个差不多印象了,接下来看下其最重要的awaitcountDown方法。

2.1 await方法

public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

 直接调用了AQS的顶级方法,再进去就是AQS的模块了

public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 获取资源,成功直接返回,失败执行下方方法(进入同步队列)
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

 简单说明一下,这个方法的意思就是调用tryAcquireShared的方法尝试获取资源,方法返回负数表示失败返回正数则表示成功失败了则入同步队列(即阻塞),具体的细节可以看下AQS的详解。

 也就是说关键点是 tryAcquireShared方法,这个方法刚才在上方已经解释过,这里再放一次。方法逻辑很简单,如果state=0(即计数完毕)则成功,否则失败。

protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

  okay,await方法的整个流程大致就是:尝试获取资源,如果失败则阻塞,成功了继续当前线程的操作。什么时候会失败呢,在state!=0的时候,而state这个变量的值我们在构造函数就已经赋予了,需要通过countDown方法来减少。

2.2 countDown

  既然这个方法这么重要,那让它开始它的表演吧。

public void countDown() {
sync.releaseShared(1);
}

 同样的,直接调用AQS的顶级释放资源的方法。

public final boolean releaseShared(int arg) {
// 如果资源释放了,那么唤醒同步队列中等待的线程
if (tryReleaseShared(arg)) {
// 善后操作
doReleaseShared();
return true;
}
return false;
}

 关键的方法还是在资源的控制上——tryReleaseShared,代码如下(上方也有):

protected boolean tryReleaseShared(int releases) {
for (;;) {
/*
* state的状态,在countDownLatch中表示剩余计数量
* 如果为0则表示可以被获取,即await方法不再阻塞
*/
int c = getState();
// 这里的意思是如果资源已经释放的情况下,就不能再次释放了,释放成功的代码在最后一行
if (c == 0)
return false;
// 本次计数后还剩余的及数量
int nextc = c-1;
// CAS设置剩余计数量
if (compareAndSetState(c, nextc))
// ==0表示锁释放了,之后state的值将一直是0,意思就是之后的await方法都不再阻塞
return nextc == 0;
}
}

  到这里countDown方法的迷雾也看清了,每一次调用countDown方法就相当于调用tryReleaseShared方法,如果当前资源还没释放的话,将state-1,判断是否为0如果为0的话表示资源释放唤醒await方法的线程,否则的话只是更新state的值。

 整理一下整个CountDownLatch的流程。

1.创建一个CountDownLatch,并赋予一个数值,这个值表示需要计数的次数,每次countDown算一次

2.在主线程调用await方法,表示需要计数器完成之前都不能动await方法的内部实现依赖于内部的AQS,调用await方法的时候会尝试去获取资源,成功条件是state=0,也就是说除非countDowncount(构造函数赋予)次之后,才能成功,失败的话当前线程进行休眠

3.在子线程调用countDown方法,每次调用都会使内部的state-1state0的时候资源释放await方法不再阻塞(即使再次调用也是)

3. 小结

  如果理解AQS的话,不止CountDownLatch,其他衍生物例如ReentrantLock都能轻易的看懂。如果不了解的话也没关系,这篇文章应该能让你对CountDownLatch的内部实现有了大概的轮廓。

  简单总结一下,CountDownLatch就三个点:构造函数的值、awaitcountDown。构造函数的值表示计数的次数,每次countDown都会使计数减一,减到0的时候await方法所在的线程就不再阻塞。







这篇文章写得,自己都有点不好意思了...

CountDownLatch是个啥?的更多相关文章

  1. 多线程条件通行工具——CountDownLatch

    CountDownLatch的作用是,线程进入等待后,需要计数器达到0才能通行. CountDownLatch(int)构造方法,指定初始计数. await()等待计数减至0. await(long, ...

  2. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  3. 同步辅助类CountDownLatch用法

    CountDownLatch是一个同步辅助类,犹如倒计时计数器,创建对象时通过构造方法设置初始值,调用CountDownLatch对象的await()方法则使当前线程处于等待状态,调用countDow ...

  4. 架构师养成记--12.Concurrent工具类CyclicBarrier和CountDownLatch

    java.util.concurrent.CyclicBarrier 一组线程共同等待,直到达到一个公共屏障点. 举个栗子,百米赛跑中,所有运动员都要等其他运动员都准备好后才能一起跑(假如没有发令员) ...

  5. Java并发之CountDownLatch

    CountDownLatch是Java concurrent包下的一个同步工具.它可以让一个(或多个)线程等待,直到其他线程中的某些操作完成. 本质上是一个信号量,我们把它比作一个有N个插销的大门,它 ...

  6. Java多线程之CountDownLatch学习

    给出官网上的例子:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html Java中conc ...

  7. 【Java】JDK类 CountDownLatch

    一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 用给定的计数 初始化 CountDownLatch.由于调用了 countDown() 方法,所以在当前计数到达 ...

  8. 【JUC】JDK1.8源码分析之CountDownLatch(五)

    一.前言 分析完了CyclicBarrier后,下面分析CountDownLatch,CountDownLatch用于同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成.CountDownL ...

  9. java多线程等待协调工作:CountDownLatch类的高级应用

    一:说明 基本上对于线程初步了解的人,都是使用synchronized来同步线程的,也确实,它也是可以满足一些常用的问题.那么我们来说一些它不能解决的问题(其实是不怎么好解决的问题,并不是真的不能解决 ...

  10. 【JAVA并发编程实战】4、CountDownLatch

    这是一个计数锁,说白了,就是当你上锁的时候,只有计数减少到0的时候,才会释放锁 package cn.xf.cp.ch05; public class TaskRunable implements R ...

随机推荐

  1. 将root用户权限赋予普通用户

    将root用户权限赋予普通用户 普通用户想要拥有root用户的权限,必须修改/etc/sudoers文件 ,还必须使用visudo命令修改.一是因为这个命令能防止多个用户同时修改这个文件,二是能进行有 ...

  2. 吃透这份pdf,面试阿里、腾讯、百度等一线大厂,顺利拿下心仪offer!

    前言 最近一位年前裸辞的朋友来找我诉苦,说因为疫情原因现在都在家吃老本.本想着年后就来找工作的,但是现在这个情况也不好找,而且很多公司也随着这次疫情面临着资金紧缺导致裁员严重的甚至倒闭,导致很多人失业 ...

  3. inspect的使用安卓动态分析工具

    一.安装步骤 1.安装xposed 2.安装inspect 二.inspect 一个基于Xposed 开发的应用动态分析工具 github已开源 内置web页面 体验度很不错 ‘ 核心功能 监控Sha ...

  4. 13.浏览器屏幕缩放bug修复

    目录 问题:浏览器缩放时,轮播图显示不全,滚动水平滚动条,发现图片缺失 解决:隐藏水平滚动条,页面都只提供垂直滚动条的需求 问题:浏览器缩放时,轮播图显示不全,滚动水平滚动条,发现图片缺失 解决:隐藏 ...

  5. WPF学习概述

    引言 在桌面开发领域,虽然在某些领域,基于electron的跨平台方案能够为我们带来某些便利,但是由于WPF技术能够更好的运用Direct3D带来的性能提升.以及海量Windows操作系统和硬件资源的 ...

  6. Windows Server 2012 R2 域证书服务搭建

    网管大叔说要给每个人颁发一个证书,这个证书很耗电 1.在服务器管理器中添加角色和功能 下一步 下一步 勾选Active Directory证书服务 下一步 下一步 勾选证书颁发机构,证书颁发机构Web ...

  7. H5页面,输入框的光标,如果页面上下滑动光标停留在页面上,除了输入框外,松手过了一段时间才跑回输入框里面

    有点类似这种情况 其中一个博主描述得比较详细,主要还有图 我是直接在App.vue主文件那里添加一下代码,主要是添加一个监听器,如果touchmove的时候就会触发让其失焦,就会消失那个光标,需要再次 ...

  8. python enumerate用法总结(转)

    原文链接:https://blog.csdn.net/churximi/article/details/51648388 enumerate()说明 enumerate()是python的内置函数en ...

  9. Swift5.2 新特性

    Print 函数传参新格式 let param = "参数" print(#"这是一个\#(param) xxxxx"#) 允许在模块中定义和标准库中名称一样的 ...

  10. Journal of Proteome Research | 人类牙槽骨蛋白的蛋白质组学和n端分析:改进的蛋白质提取方法和LysargiNase消化策略增加了蛋白质组的覆盖率和缺失蛋白的识别 | (解读人:卜繁宇)

    文献名:Proteomic and N-Terminomic TAILS Analyses of Human Alveolar Bone Proteins: Improved Protein Extr ...