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

并发编程系列博客传送门

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. Ruby中的Hash(哈希),你可以理解为字典

    原文链接 以下代码在Ruby 2.5.1中编译通过 定义 myHash = Hash.new myHash1 = Hash["key1" => 100, "key2 ...

  2. iOS 指纹认证登陆开发(TouchID)

    设计思路 TouchID 关联账号 用户登陆成功 -> 开启TouchID登陆 -> TouchID验证 -> 记录用户信息(验证通过) -> 关联完成 TouchID 登陆 ...

  3. 安装SQL Server 2008R2 报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本”解决方法

    安装SQL Server 2008 R2报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本,请在安装 SQL Server 2008 前将 VS2008 升级 ...

  4. Jmeter接口测试之用户自定义变量(九)

    在使用Jmeter做接口自动化测试中,经常会使用到公共的数据,那么就需要对这些公共的数据分离出来,不管是基于测试框架的思想,还是使用工具来进行做自动化测试,公共数据的分离首先是需要思考的.这里就以获取 ...

  5. Excel中减少两边的字符

    从右边减少几个字符: =LEFT(A1,LEN(A1)-4)

  6. coding++:Spring_IOC(控制反转)详解

    IoC是什么: 1):Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想. 2):在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的 ...

  7. SpringCloud服务的注册发现--------consul实现服务与发现

    1,consul也可以替代Eureka实现注册和发现的功能,即注册中心. 之前在linux环境通过consul + upsync + nginx 实现nginx 的动态负载均衡 https://www ...

  8. python中的原地操作

    什么是原地操作: 例子: 列表在append添加一个元素后,没有产生新副本,再次打印的时候多了一个值,这个appned就是原地操作 由此可见,原地操作有以下特点: 没有返回值(返回值为None) 改变 ...

  9. python 基本知识

    1.windows下Spyder中快捷键 块注释/块反注释 Ctrl + 4/5 断点设置 F12 关闭所有 Ctrl + Shift + W 代码完成 Ctrl +空格键 条件断点 SHIFT + ...

  10. 用c#求一元二次方程

    题目:编一个程序,输入a .b.c 的值,求出一元二次方程a*x*x+b*x+c=0的二个实数根. 我的思路: 我们都知道数学中求一元二次方程有很多方法:直接开方法.配方法.公式法.分解因式法等等,在 ...