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

并发编程系列博客传送门

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. 常用的 Git 命令与场景

    Git 分布式版本控制系统 它拥有完整的版本控制功能,能够解决多人协作的问题 将自己的代码同步到 Github 上能够提升开发效率 git 会记录你每一次的版本修改操作 常用的 Git 操作 # 指定 ...

  2. 死磕Lambda表达式(四):常用的函数式接口

    失去人性,失去很多:失去兽性,失去一切.--<三体> 在Java8支持Lambda表达式以后,为了满足Lambda表达式的一些典型使用场景,JDK为我们提供了大量常用的函数式接口.它们主要 ...

  3. Android开发:通过 webview 将网页打包成安卓应用

    商业转载请联系作者获得授权,非商业转载请注明出处. For commercial use, please contact the author for authorization. For non-c ...

  4. hdu3368 dfs 下棋

    两颗黑子之间的白子可以翻装成黑子,两颗白子之间的黑子可以翻转成白子,对于一个给定位置,有八个方向有翻转其他颜色的子的可能.规则之一是下棋的位置一定要能翻转对方的子. 求最优情况:黑子能翻转的白子个数的 ...

  5. 题解 P4344 【[SHOI2015]脑洞治疗仪】

    前言 这道题目呢,看上去很难,实际上我们可以用线段树解决这道题目. 正文 我们维护 sum.len.tag.lmax.rmax.ans. sum 就是这段区间非脑洞的个数 len 就是这段区间的长度 ...

  6. 【洛谷P1801】黑匣子——优先队列

    题目链接 一道有点意思的题目 我们可以维护两个优先队列:pqmin和pqmax 其中 pqmin 是小根堆, pqmax 是大根堆 每次 add 一个数字,则将数字推入到 pqmin 中 每次 get ...

  7. Prism 源码解读2-View的加载和控制

    介绍 上一篇介绍了Region,这一篇跟Region息息相关,讲一下Region中View的加载方式及控制. 4.ViewDiscovery 在创建好Region后需要将View添加到Region中. ...

  8. 企业级自动化部署方案——ansible实现tomcat自动安装和配置

    共耗时10多个小时 思路一 总体设计 ansible-playbook目录结构 [root@ansible ~]# tree /etc/ansible/roles/tomcat /etc/ansibl ...

  9. Mysql性能优化:什么是索引下推?

    导读 索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询. 在不使用ICP的情况下,在使用非主键索引(又叫普通索引或者二级索引)进行 ...

  10. 高数解题神器:拍照上传就出答案,这个中国学霸做的AI厉害了 | Demo

    一位叫Roger的中国学霸小哥的拍照做题程序mathAI一下子火了,这个AI,堪称数学解题神器. 输入一张包含手写数学题的图片,AI就能识别出输入的数学公式,然后给出计算结果. 不仅加减乘除基本运算, ...