CountDownLatch简介

CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程操作执行完成。

使用场景:

在开发过程中,经常会遇到需要在主线程中开启多条线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景,
CountDownLatch的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须大于0。另外它还提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0,
它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

CountDownLatch原理

CountDownLatch底层依靠的是AQS,通过构造函数初始化计数器时,实际上是
把计数器的值赋值给了AQS的state,也就是这里AQS的状态值来表示计数器值。

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

await方法

void await()方法,当前线程调用了CountDownLatch对象的await方法后,当前线程会被阻塞,直到出现下面情况之一时才会返回:

  1. 当所有线程都调用了CountDownLatch对象的countDown方法后,也就是说计时器值为 0 的时候。
  2. 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程会抛出InterruptedException异常后返回。接下来让我们看看await()方法内部是如何调用
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//AQS的获取共享资源时候可被中断的方法
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {
//如果线程被中断则抛异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试看当前是否计数值为0,为0则直接返回,否者进入AQS的队列等待
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
} //sync类实现的AQS的接口
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}

从上面代码可以看到await()方法委托sync调用了AQS的acquireSharedInterruptibly方法,该方法的特点是线程获取资源的时候可以被中断,并且获取到的资源是共享资源,这里为什么要调用AQS的这个方法,而不是调用独占锁的accquireInterruptibly方法呢?这是因为这里状态值需要的并不是非 0 即 1 的效果,而是和初始化时候指定的计数器值有关系,比如你初始化的时候计数器值为 8 ,那么state的值应该就有 0 到 8 的状态,而不是只有  0  和  1 的独占效果。

这里await()方法调用acquireSharedInterruptibly的时候传递的是 1 ,就是说明要获取一个资源,而这里计数器值是资源总数,也就是意味着是让总的资源数减 1 ,acquireSharedInterruptibly内部首先判断如果当前线程被中断了则抛出异常,否则调用sync实现的tryAcquireShared方法看当前状态值(计数器值)是否为 0  ,是则当前线程的await()方法直接返回,否则调用AQS的doAcquireSharedInterruptibly让当前线程阻塞。另外调用tryAcquireShared的方法仅仅是检查当前状态值是不是为 0 ,并没有调用CAS让当前状态值减去 1 。

boolean await(long timeout, TimeUnit unit)

当线程调用了 CountDownLatch 对象的该方法后,当前线程会被阻塞,直到出现下面情况之一时才会返回:

  1. 当所有线程都调用了 CountDownLatch 对象的 countDown 方法后,也就是计时器值为 0 的时候,这时候返回 true.
  2. 设置的 timeout 时间到了,因为超时而返回 false.
  3. 其它线程调用了当前线程的 interrupt()方法中断了当前线程,当前线程会抛出 InterruptedException 异常后返回。
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

countDown方法

void countDown() 当前线程调用了该方法后,会递减计数器的值,递减后如果计数器为 0 则会唤醒所有调用await 方法而被阻塞的线程,否则什么都不做。

public void countDown() {
//委托sync调用AQS的方法
sync.releaseShared(1);
}
//AQS的方法
public final boolean releaseShared(int arg) {
//调用sync实现的tryReleaseShared
if (tryReleaseShared(arg)) {
//AQS的释放资源方法
doReleaseShared();
return true;
}
return false;
}

如上面代码可以知道CountDownLatch的countDown()方法是委托sync调用了AQS的releaseShared方法,后者调用了sync 实现的AQS的tryReleaseShared

protected boolean tryReleaseShared(int releases) {
//循环进行cas,直到当前线程成功完成cas使计数值(状态值state)减一并更新到state
for (;;) {
int c = getState(); //如果当前状态值为0则直接返回(1)
if (c == 0)
return false; //CAS设置计数值减一(2)
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}

如上代码可以看到首先获取当前状态值(计数器值),代码(1)如果当前状态值为 0 则直接返回 false ,则countDown()方法直接返回;否则执行代码(2)使用CAS设置计数器减一,CAS失败则循环重试,否则如果当前计数器为 0 则返回 true 。返回 true 后,说明当前线程是最后一个调用countDown()方法的线程,那么该线程除了让计数器减一外,还需要唤醒调用CountDownLatch的await 方法而被阻塞的线程。这里的代码(1)貌似是多余的,其实不然,之所以添加代码 (1) 是为了防止计数器值为 0 后,其他线程又调用了countDown方法,如果没有代码(1),状态值就会变成负数。

getCount()方法

long getCount() 获取当前计数器的值,也就是 AQS 的 state 的值。

public long getCount() {
return sync.getCount();
} int getCount() {
return getState();
}

如上代码可知内部还是调用了 AQS 的 getState 方法来获取 state 的值(计数器当前值)。

使用方法(案例)

public class CountDownLatchTest {

    private static AtomicInteger id = new AtomicInteger();

    // 创建一个CountDownLatch实例,管理计数为ThreadNum
private static volatile CountDownLatch countDownLatch = new CountDownLatch(3); public static void main(String[] args) throws InterruptedException { Thread threadOne = new Thread(new Runnable() { @Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println("【玩家" + id.getAndIncrement() + "】已入场");
countDownLatch.countDown();
}
}); Thread threadTwo = new Thread(new Runnable() { @Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println("【玩家" + id.getAndIncrement() + "】已入场");
countDownLatch.countDown(); }
}); Thread threadThree = new Thread(new Runnable() { @Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println("【玩家" + id.getAndIncrement() + "】已入场");
countDownLatch.countDown(); }
}); // 启动子线程
threadOne.start();
threadTwo.start();
threadThree.start();
System.out.println("等待斗地主玩家进场"); // 等待子线程执行完毕,返回
countDownLatch.await(); System.out.println("斗地主玩家已经满人,开始发牌....."); }
}
OutPut:
等待斗地主玩家进场
【玩家0】已入场
【玩家1】已入场
【玩家2】已入场
斗地主玩家已经满人,开始发牌.....

如上代码,创建了一个 CountDownLatch 实例,因为有三个子线程所以构造函数参数传递为 3,主线程调用 countDownLatch.await()方法后会被阻塞。子线程执行完毕后调用countDownLatch.countDown() 方法让 countDownLatch 内部的计数器减一,等所有子线程执行完毕调用 countDown()后计数器会变为 0,这时候主线程的 await()才会返回。

CountDownLatch 与 join 方法的区别,一个区别是调用一个子线程的 join()方法后,该线程会一直被阻塞直到该线程运行完毕,而 CountDownLatch 则使用计数器允许子线程运行完毕或者运行中时候递减计数,也就是 CountDownLatch 可以在子线程运行任何时候让 await 方法返回而不一定必须等到线程结束;另外使用线程池来管理线程时候一般都是直接添加 Runable 到线程池这时候就没有办法在调用线程的 join 方法了,countDownLatch 相比 Join 方法让我们对线程同步有更灵活的控制。

转自: https://www.omgleoo.top/%E4%B8%80%E7%AF%87%E5%85%B3%E4%BA%8Ecountdownlatch%E7%9A%84%E5%A5%BD%E6%96%87%E7%AB%A0/

一篇关于CountDownLatch的好文章的更多相关文章

  1. java并发编程JUC第九篇:CountDownLatch线程同步

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...

  2. 前两篇转载别人的精彩文章,自己也总结一下python split的用法吧!

    前言:前两篇转载别人的精彩文章,自己也总结一下吧! 最近又开始用起py,是为什么呢? 自己要做一个文本相似度匹配程序,大致思路就是两个文档,一个是试题,一个是材料,我将试题按每题分割出来,再将每题的内 ...

  3. 一篇关于PHP性能的文章

    一篇关于PHP性能的文章 昨晚清理浏览器收藏夹网址时,发现了http://www.phpbench.com/,想起来应该是2015年发现的一个比较性能的文章,我就点进去看了看,发现还是全英文耶,刚好最 ...

  4. 几篇关于RGBD语义分割文章的总结

      最近在调研3D算法方面的工作,整理了几篇多视角学习的文章.还没调研完,先写个大概.   基于RGBD的语义分割的工作重点主要集中在如何将RGB信息和Depth信息融合,主要分为三类:省略. 目录 ...

  5. 推荐几篇关于EF的好文章

    文章作者 Julie Lerman 是 Microsoft MVP..NET 导师和顾问,住在佛蒙特州的山区.您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示.她的博客地址是 ...

  6. 一篇入门的php Class 文章

    刚在大略浏览了一下首页更新的那篇有关Class的文章(指PHPE的那篇 http://www.phpe.net/articles/389.shtml ),很不错,建议看看.  对类的摸索--俺用了半年 ...

  7. 国内首篇介绍JanOS物联网操作系统的文章 - 如何把你的手机主板打造成物联网平台

    天地会珠海分舵注:如无意外,您现在正在看的将是国内首篇且是唯一一篇介绍炙手可热的物联网的操作系统JanOS的文章!不信你去百度!希望大家能喜欢.但本文只是引言,更多信息请还是访问JanOS的官网:ht ...

  8. 几篇QEMU/KVM代码分析文章

    QEMU/KVM结合起来分析的几篇文章,代码跟最新的版本有些差异,但大体逻辑一样,写得通俗易懂.我把链接放这里主要是为自己需要查看时调转过去方便,感谢作者的付出! QEMU Source Code S ...

  9. HelloDjango 第 08 篇:开发博客文章详情页

    作者:HelloGitHub-追梦人物 文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 首页展示的是所有文章的列表,当用户看到感兴趣的文章时,他点击文章的标题或者继续阅读的按 ...

随机推荐

  1. HDU2476 String painter——区间DP

    题意:要把a串变成b串,每操作一次,可以使a串的[l,r]区间变为相同的一个字符.问把a变成b最少操作几次. 这题写法明显是区间dp ,关键是处理的方法. dp[l][r]表示b串的l~r区段至少需要 ...

  2. Codefoces 277 E. Binary Tree on Plane

    题目链接:http://codeforces.com/problemset/problem/277/E 参考了这篇题解:http://blog.csdn.net/Sakai_Masato/articl ...

  3. TypeScript作业

    题目: 了解神话故事盘古开天辟地或者女娲开世造物,通过typescript程序模拟出天地的变化过程或者万物的衍生过程 参考博客园大神: https://www.cnblogs.com/tansm/p/ ...

  4. 【NET Core】事务TransactionScope

    .NET FrameWork时期: TransactionScope是FCL System.Transactions命名空间下的分布式事务组件,它默认为本地事务,当系统有需要时可以自动提升为分布式事务 ...

  5. collection.Counter

    a=['A','B','C','A','D','E','W','A','B'] b=collections.Counter(a)  # 可以统计a中的各个元素出现的次数print(b)print(b[ ...

  6. 安装Go插件遇到的问题及解决方法

    1. 问题:在 Windows 平台下使用 go get 安装sqlite3 驱动时报错 The remote end hung up unexpectedly ? 原因及解决方法: 原因可能有两种: ...

  7. [Hibernate] inner Join和 left Join

    @Test public void test11(){ Session ss=HibernateUtil.getSession(); //根据员工名称(SCOTT)找到和他所在的部门的其他员工的信息 ...

  8. PXE网络启动无人值守自动安装 centos 全程实录

    PXE网络启动无人值守自动安装 centos 全程实录 http://shayi1983.blog.51cto.com/4681835/1549854/ 搭建Pxe服务器无人听应答全自动安装CentO ...

  9. ES6 新加的类型Symbol()

    Symbol()如果一个对象中的键已经存在,但目前我们目前不知道这个键是存在的,然后我们去给这个键赋值,Symbol()就不会改变这个键对应的值

  10. Android Studio酷炫插件(一)——自动化快速实现Parcelable接口序列化

    https://blog.csdn.net/kroclin/article/details/40902721 一.前言相信数据序列化大家都多多少少有接触到,比如自定义了一个实体类,需要在activit ...