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

并发编程系列博客传送门

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. img标签src="[object Moudle]"问题解决办法

    在写组件的时候,按照如下方式来写,加载不出图片: 打开开发者工具,发现img标签对应位置显示如下: 我之前已经下载过 url-loader 解决方法如下: 加入 esModules:false 即可

  2. WordCount程序(Java)

    Github项目地址:https://github.com/softwareCQT/web_camp/tree/master/wordCount 一.题目描述 实现一个简单而完整的软件工具(源程序特征 ...

  3. Frist

    大家好,这是我开通博客的第一天,开通博客呢原因是在于想提升自己.我现在是一名大一的计算机科学与技术的学生,自己本来就对计算机感兴趣,现在想通过博客园这个平台写一些自己感兴趣的文章.现在主要在学java ...

  4. SQL数据库-基本操作

    SQL教程 整理自:廖雪峰的官方网站-SQL教程 目录 SQL教程 SQL快捷键 1.概述 数据类型 SQL操作数据库的能力 语法特点 2. 安装MySQL 运行MySQL 3. 关系模型 3.1 概 ...

  5. 逻辑回归和sigmoid函数分类

    逻辑回归和sigmoid函数分类:容易欠拟合,分类精度不高,计算代价小,易于理解和实现 sigmoid函数与阶跃函数的区别在于:阶跃函数从0到1的跳跃在sigmoid函数中是一个逐渐的变化,而不是突变 ...

  6. doc-指令-查看端口是否被占用及占用程序

    来源:http://www.blogjava.net/huozhicheng/archive/2011/09/27/359620.html 1.首先进入命令行 查看端口是否被占用 使用命令: nets ...

  7. (线段树 -星星等级)Stars POJ - 2352

    题意: 给出n个星星的坐标 x,y ,当存在其他星星的坐标x1,y1满足x>=x1&&y>=y1时 这个星星的等级就加1. 注意: 题中给的数据是有规律的 ,y是逐渐增加的 ...

  8. class-dump的安装和使用

    安装步骤 1.下载地址:http://stevenygard.com/projects/class-dump/ 2.打开终端输入 open /usr/local/bin 3.把dmg文件中的class ...

  9. ArrayList中的Iterator详解

    每个实现Iterable接口的类必须提供一个iterator方法,返回一个Iterator对象,ArrayList也不例外 public Iterator<E> iterator() { ...

  10. Dome 多人人脸识别 face_recognition

    Dome 多人人脸识别 face_recognition 注意 face_recognition 依赖 face_recognition_models 中文字体文件需要自己下载 1.多人人脸识别 # ...