本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

并发编程系列博客传送门

CountDownLatch简介

CountDownLatch是JDK并发包中提供的一个同步工具类。官方文档对这个同步工具的介绍是:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

上面的英文介绍大致意思是:CountDownLatch的主要功能是让一个或者多个线程等待直到一组在其他线程中执行的操作完成。

使用列子

观看上面的解释可能并不能直观的说明CountDownLatch的作用,下面我们通过一个简单的列子看下CountDownLatch的使用。

场景:主人(主线程)请客人(子线程)吃晚饭,需要等待所有客人都到了之后才开饭。我们用CountDownLatch来模拟下这个场景。

public class CountDownLatchDemo {

    private static final int PERSON_COUNT = 5;

    private static final CountDownLatch c = new CountDownLatch(PERSON_COUNT);

    public static void main(String[] args) throws InterruptedException {
System.out.println("l am master, waiting guests...");
for (int i = 0; i < PERSON_COUNT; i++) {
int finalI = i;
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" l am person["+ finalI +"]");
TimeUnit.MILLISECONDS.sleep(500);
//System.out.println(Thread.currentThread().getName()+" count:"+c.getCount());
c.countDown();
}
}).start();
}
c.await();
System.out.println("all guests get, begin dinner...");
} }

上面的列子中,主人(master线程)请了5个客人吃饭,每个客人到了之后会将CountDownLatch的值减一,主人(master)会一直等待所有客人到来,最后输出”开饭“。

CountDownLatch的使用方式很简单,下面来看下它的实现原理。

原理剖析

首先我们先看下CountDownLatch重要的API

- getCount():获取当前count的值。
- wait():让当前线程在此CountDownLatch对象上等待,可以中断。与notify()、notifyAll()方法对应。
- await():让当前线程等待此CountDownLatch对象的count变为0,可以中断。
- await(timeout,TimeUnit):让当前线程等待此CountDownLatch对象的count变为0,可以超时、可以中断。
- countDown():使此CountDownLatch对象的count值减1(无论执行多少次,count最小值为0)。

下面我们看下具体API的源代码

构造函数

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

在构建CountDownLatch对象时需要传入一个int型的初始值,这个值就是计数器的初始值。从上面的代码中可以看出,创建CountDownLatch是new了一个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;
}
}
}

Sync对象是基于AQS机制实现的,自己实现了tryAcquireSharedtryReleaseShared方法。

await方法


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

调用await方法其实是调用了AQS的acquireSharedInterruptibly方法。

 public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

acquireSharedInterruptibly中先判断了下当前线程有没有被中断,假如线程已经被中断了,直接抛出中断异常。否则进入doAcquireSharedInterruptibly

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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

doAcquireSharedInterruptibly的处理逻辑是先判断队列中是否只有当前线程,如果只有当前线程的先尝试获取下资源,如果获取资源成功就直接返回了。获取资源不成功就判断下是否要park当前线程,如果需要park当前线程,

那么当前线程就进入waiting状态。否则在for循环中一直执行上面的逻辑。

countDown方法

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

熟悉AQS机制的会知道上面的代码其实也是调的AQS的releaseSharedreleaseShared的方法会调到Sync中的tryReleaseShared

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;
}
}

上面的代码逻辑很简单:status的值是0的话就返回true,否则返回false。返回true的话,就会唤醒AQS队列中所有阻塞的线程。

使用场景

  • 场景一:将任务分割成多个子任务,每个子任务由单个线程去完成,等所有线程完成后再将结果汇总。(MapReduce)这种场景下,CountDoenLatch作为一个完成信号来使用。
  • 场景二:多个线程等到,一直等到某个条件发生。比如多个赛跑运动员都做好了准备,就等待裁判手中的发令枪响。这种场景下,就可以将CountdownLatch的初始值设置成1。

简单总结

  • CountDownLatch的初始值不能重置,只能减少不能增加,最多减少到0;
  • CountDownLatch计数值没减少到0之前,调用await方法可能会让调用线程进组一个阻塞队列,直到计数值减小到0;
  • 调用countDown方法会让计数值每次都减小1,但是最多减少到0。当CountDownLatch的计数值减少到0的时候,会唤醒所有在阻塞队列中的线程。

同步工具类—— CountDownLatch的更多相关文章

  1. 同步工具类 CountDownLatch 和 CyclicBarrier

    在开发中,一些异步操作会明显加快执行速度带来更好的体验,但同时也增加了开发的复杂度,想了用好多线程,就必须从这些方面去了解 线程的 wait() notify() notifyall() 方法 线程异 ...

  2. 并发是个什么鬼之同步工具类CountDownLatch

    扯淡 写这篇文章,我先酝酿一下,实不相瞒,脱离底层太久了,更确切的情况是,真没曾认真研究过.就目前来说,很多框架包括工具类已经把实现封装的很深,你只需轻轻的调用一下API,便不费半点力气.以至于大家会 ...

  3. JUC常用同步工具类——CountDownLatch,CyclicBarrier,Semaphore

    在 JUC 下包含了一些常用的同步工具类,今天就来详细介绍一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它们之间的区别. 一.CountDownLa ...

  4. 并发包下常见的同步工具类(CountDownLatch,CyclicBarrier,Semaphore)

    在实际开发中,碰上CPU密集且执行时间非常耗时的任务,通常我们会选择将该任务进行分割,以多线程方式同时执行若干个子任务,等这些子任务都执行完后再将所得的结果进行合并.这正是著名的map-reduce思 ...

  5. 深入分析同步工具类之AbstractQueuedSynchronizer

      概览: AQS(简称)依赖内部维护的一个FIFO(先进先出)队列,可以很好的实现阻塞.同步:volatile修饰的属性state,哪个线程先改变这个状态值,那么这个线程就获得了优先权,可以做任何事 ...

  6. 【同步工具类】CountDownLatch

    闭锁是一种同步工具类,可以延迟线程的进度直到其达到终止状态. 作用:相当于一扇门,在到达结束状态之前,这扇门一直是关闭的,并且没有任务线程能够通过,当到达结束状态时,这扇门会打开并允许所有的线程通过, ...

  7. 深入分析同步工具类之CountDownLatch

    概览: CountDownLatch又称闭锁,其作用是让一个或者多个线程挂起,直到其他的线程执行完后恢复挂起的线程,使其继续执行.内部维护着一个静态内部类Sync,该类继承AbstractQueued ...

  8. Java多线程同步工具类之CountDownLatch

    在过去我们实现多线程同步的代码中,往往使用join().wait().notiyAll()等线程间通信的方式,随着JUC包的不断的完善,java为我们提供了丰富同步工具类,官方也鼓励我们使用工具类来实 ...

  9. 【同步工具类】CountDownLatch闭锁任务同步

    [同步工具类]CountDownLatch闭锁任务同步 转载:https://www.cnblogs.com/yangchongxing/p/9214284.html 打过dota的同学都知道,多人一 ...

随机推荐

  1. 网络安全从入门到精通 (第二章-6) 后端基础PHP—表单验证

    本文内容: 什么是表单? 如何创建一个表单: 接收并验证: PHP和数据库交互 1,什么事表单? 表单在网页中主要负责数据采集. 表单由三部分组成: 表单标签:这里面包含了处理表单数据所用动态脚本的U ...

  2. C# datagridview 格式化单元格内容

    private void dgvBig_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) {  if (dgvB ...

  3. 微信开发+百度AI学习:植物识别

    直接上代码 服务端代码如下 private static readonly Baidu.Aip.ImageClassify.ImageClassify client = new Baidu.Aip.I ...

  4. 最小生成树的Prim算法以及Kruskal算法的证明

    Prime算法的思路:从任何一个顶点开始,将这个顶点作为最小生成树的子树,通过逐步为该子树添加边直到所有的顶点都在树中为止.其中添加边的策略是每次选择外界到该子树的最短的边添加到树中(前提是无回路). ...

  5. 【codeforces】Codeforces Round #612 (Div. 2) C. Garland——DP

    题目链接 贪心模拟了半天,最后放弃了 题意 给你一串从1−n1-n1−n的序列,其中部分未知(表示为0),补全序列使得相邻数值奇偶性相反的数量最少 相邻数值的奇偶性相反:两个相邻的两个数值,其中一个为 ...

  6. Asp.Net Core 学习教程2、使用ASP.NET Core中的RazorPages

    1.创建一个Asp.Net Core Web应用程序 1.1.打开VS2019 新建项目 1.2.选好项目位置后进入线面界面,选择Web应用程序 1.3.进去的页面结构如下 Pages 文件夹:包含 ...

  7. Javascript之盒子拖拽(跟随鼠标、边界限定、轨迹回放)

    本文通过拖拽案例,实现"跟随鼠标.边界限定.轨迹回放"三大效果: 完整代码中有详尽注释,故不再进行细致讲解: 对于案例中需要注意的重点或易错点问题,会总结在最后. 效果图(仅演示左 ...

  8. test命令的使用以及判断语法

    test命令 Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值.字符和文件三个方面的测试. 语法:test EXPRESSION 或者 [ EXPRESSION ] 字符串判断( ...

  9. webpack配置打包vue文件

    1.首先全局安装node,和npm.检查是否安装成功 2.新建一个文件下,进入该文件夹. 前先执行 npm init -y 然后就会在文件夹下出现一个package.json文件 然后执行 npm i ...

  10. CSS 常用属性之 阴影

    text-shadow 是一个给文字添加阴影的属性 text-shadow: X偏移量,Y偏移量,模糊值,颜色  可以同时设置多个文本阴影,需要用逗号隔开 如果是需要兼容低版本的浏览器 -webkit ...