作者:chenssy

来源:Java技术驿站

在上篇博客中介绍了Java四大并发工具一直的CyclicBarrier,今天要介绍的CountDownLatch与CyclicBarrier有点儿相似。

CyclicBarrier所描述的是“允许一组线程互相等待,直到到达某个公共屏障点,才会进行后续任务”,而CountDownLatch所描述的是”在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待“。在API中是这样描述的:

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

CountDownLatch是通过一个计数器来实现的,当我们在new 一个CountDownLatch对象的时候需要带入该计数器值,该值就表示了线程的数量。每当一个线程完成自己的任务后,计数器的值就会减1。当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程继续执行了。

虽然,CountDownlatch与CyclicBarrier有那么点相似,但是他们还是存在一些区别的:

  1. CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待

  2. CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier


实现分析

CountDownLatch结构如下

通过上面的结构图我们可以看到,CountDownLatch内部依赖Sync实现,而Sync继承AQS。CountDownLatch仅提供了一个构造方法:

CountDownLatch(int count) : 构造一个用给定计数初始化的 CountDownLatch

  1.    public CountDownLatch(int count) {

  2.        if (count < 0) throw new IllegalArgumentException("count < 0");

  3.        this.sync = new Sync(count);

  4.    }

sync为CountDownLatch的一个内部类,其定义如下:

  1. private static final class Sync extends AbstractQueuedSynchronizer {

  2.        private static final long serialVersionUID = 4982264981922014374L;

  3.        Sync(int count) {

  4.            setState(count);

  5.        }

  6.        //获取同步状态

  7.        int getCount() {

  8.            return getState();

  9.        }

  10.        //获取同步状态

  11.        protected int tryAcquireShared(int acquires) {

  12.            return (getState() == 0) ? 1 : -1;

  13.        }

  14.        //释放同步状态

  15.        protected boolean tryReleaseShared(int releases) {

  16.            for (;;) {

  17.                int c = getState();

  18.                if (c == 0)

  19.                    return false;

  20.                int nextc = c-1;

  21.                if (compareAndSetState(c, nextc))

  22.                    return nextc == 0;

  23.            }

  24.        }

  25.    }

通过这个内部类Sync我们可以清楚地看到CountDownLatch是采用共享锁来实现的。

await()

CountDownLatch提供await()方法来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断,定义如下:

  1.    public void await() throws InterruptedException {

  2.        sync.acquireSharedInterruptibly(1);

  3.    }

await其内部使用AQS的acquireSharedInterruptibly(int arg):

  1.    public final void acquireSharedInterruptibly(int arg)

  2.            throws InterruptedException {

  3.        if (Thread.interrupted())

  4.            throw new InterruptedException();

  5.        if (tryAcquireShared(arg) < 0)

  6.            doAcquireSharedInterruptibly(arg);

  7.    }

在内部类Sync中重写了tryAcquireShared(int arg)方法:

  1.        protected int tryAcquireShared(int acquires) {

  2.            return (getState() == 0) ? 1 : -1;

  3.        }

getState()获取同步状态,其值等于计数器的值,从这里我们可以看到如果计数器值不等于0,则会调用doAcquireSharedInterruptibly(int arg),该方法为一个自旋方法会尝试一直去获取同步状态:

  1.  private void doAcquireSharedInterruptibly(int arg)

  2.            throws InterruptedException {

  3.        final Node node = addWaiter(Node.SHARED);

  4.        boolean failed = true;

  5.        try {

  6.            for (;;) {

  7.                final Node p = node.predecessor();

  8.                if (p == head) {

  9.                    /**

  10.                     * 对于CountDownLatch而言,如果计数器值不等于0,那么r 会一直小于0

  11.                     */

  12.                    int r = tryAcquireShared(arg);

  13.                    if (r >= 0) {

  14.                        setHeadAndPropagate(node, r);

  15.                        p.next = null; // help GC

  16.                        failed = false;

  17.                        return;

  18.                    }

  19.                }

  20.                //等待

  21.                if (shouldParkAfterFailedAcquire(p, node) &&

  22.                        parkAndCheckInterrupt())

  23.                    throw new InterruptedException();

  24.            }

  25.        } finally {

  26.            if (failed)

  27.                cancelAcquire(node);

  28.        }

  29.    }

countDown()

CountDownLatch提供countDown() 方法递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

  1.    public void countDown() {

  2.        sync.releaseShared(1);

  3.    }

内部调用AQS的releaseShared(int arg)方法来释放共享锁同步状态:

  1.    public final boolean releaseShared(int arg) {

  2.        if (tryReleaseShared(arg)) {

  3.            doReleaseShared();

  4.            return true;

  5.        }

  6.        return false;

  7.    }

tryReleaseShared(int arg)方法被CountDownLatch的内部类Sync重写:

  1.    protected boolean tryReleaseShared(int releases) {

  2.        for (;;) {

  3.            //获取锁状态

  4.            int c = getState();

  5.            //c == 0 直接返回,释放锁成功

  6.            if (c == 0)

  7.                return false;

  8.            //计算新“锁计数器”

  9.            int nextc = c-1;

  10.            //更新锁状态(计数器)

  11.            if (compareAndSetState(c, nextc))

  12.                return nextc == 0;

  13.        }

  14.    }

总结

CountDownLatch内部通过共享锁实现。在创建CountDownLatch实例时,需要传递一个int型的参数:count,该参数为计数器的初始值,也可以理解为该共享锁可以获取的总次数。当某个线程调用await()方法,程序首先判断count的值是否为0,如果不会0的话则会一直等待直到为0为止。当其他线程调用countDown()方法时,则执行释放共享锁状态,使count值 – 1。当在创建CountDownLatch时初始化的count参数,必须要有count线程调用countDown方法才会使计数器count等于0,锁才会释放,前面等待的线程才会继续运行。注意CountDownLatch不能回滚重置。

关于共享锁的请参考:【死磕Java并发】—–J.U.C之AQS:同步状态的获取与释放

应用示例

示例仍然使用开会案例。老板进入会议室等待5个人全部到达会议室才会开会。所以这里有两个线程老板等待开会线程、员工到达会议室:

  1. public class CountDownLatchTest {

  2.    private static CountDownLatch countDownLatch = new CountDownLatch(5);

  3.    /**

  4.     * Boss线程,等待员工到达开会

  5.     */

  6.    static class BossThread extends Thread{

  7.        @Override

  8.        public void run() {

  9.            System.out.println("Boss在会议室等待,总共有" + countDownLatch.getCount() + "个人开会...");

  10.            try {

  11.                //Boss等待

  12.                countDownLatch.await();

  13.            } catch (InterruptedException e) {

  14.                e.printStackTrace();

  15.            }

  16.            System.out.println("所有人都已经到齐了,开会吧...");

  17.        }

  18.    }

  19.    //员工到达会议室

  20.    static class EmpleoyeeThread  extends Thread{

  21.        @Override

  22.        public void run() {

  23.            System.out.println(Thread.currentThread().getName() + ",到达会议室....");

  24.            //员工到达会议室 count - 1

  25.            countDownLatch.countDown();

  26.        }

  27.    }

  28.    public static void main(String[] args){

  29.        //Boss线程启动

  30.        new BossThread().start();

  31.        for(int i = 0 ; i < countDownLatch.getCount() ; i++){

  32.            new EmpleoyeeThread().start();

  33.        }

  34.    }}

运行结果: 

- END -

 往期推荐:

  • 死磕Java系列:

  1. 深入分析ThreadLocal

  2. 深入分析synchronized的实现原理

  3. 深入分析volatile的实现原理

  4. Java内存模型之happens-before

  5. Java内存模型之重排序

  6. Java内存模型之分析volatile

  7. Java内存模型之总结

  8. J.U.C之AQS简介

  9. J.U.C之AQS:CLH同步队列

  10. J.U.C之AQS同步状态的获取与释放

  11. J.U.C之AQS阻塞和唤醒线程

  12. J.U.C之重入锁:ReentrantLock

……

  • Spring系列:

  1. Spring Cloud Zuul中使用Swagger汇总API接口文档

  2. Spring Cloud Config Server迁移节点或容器化带来的问题

  3. Spring Cloud Config对特殊字符加密的处理

  4. Spring Boot使用@Async实现异步调用:使用Future以及定义超时

  5. Spring Cloud构建微服务架构:分布式配置中心(加密解密)

  6. Spring Boot快速开发利器:Spring Boot CLI

……

可关注我的公众号

深入交流、更多福利

扫码加入我的知识星球

点击“阅读原文”,看本号其他精彩内容

死磕Java并发:J.U.C之并发工具类:CountDownLatch相关推荐

  1. 14、详解java同步工具类CountDownLatch

    这篇文章主要讲解java中一个比较常用的同步工具类CountDownLatch,不管是在工作还是面试中都比较常见.我们将通过案例来进行讲解分析. 一.定义 CountDownLatch的作用很简单,就 ...

  2. 死磕Java并发:J.U.C之阻塞队列:ArrayBlockingQueue

    作者:chenssy 来源:Java技术驿站 ArrayBlockingQueue,一个由数组实现的有界阻塞队列.该队列采用FIFO的原则对元素进行排序添加的. ArrayBlockingQueue为 ...

  3. 死磕Java并发:J.U.C之Java并发容器:ConcurrentLinkedQueue

    作者:chenssy 来源:Java技术驿站 要实现一个线程安全的队列有两种方式:阻塞和非阻塞.阻塞队列无非就是锁的应用,而非阻塞则是CAS算法的应用.下面我们就开始一个非阻塞算法的研究:Coucur ...

  4. 死磕Java并发:J.U.C之并发工具类:Exchanger

    作者:chenssy 来源:Java技术驿站 前面三篇博客分别介绍了CyclicBarrier.CountDownLatch.Semaphore,现在介绍并发工具类中的最后一个Exchange.Exc ...

  5. 死磕Java并发:J.U.C之Condition

    作者:chenssy 来源:http://cmsblogs.com/?p=2222 在没有Lock之前,我们使用synchronized来控制同步,配合Object的wait().notify()系列 ...

  6. 死磕Java并发:J.U.C之读写锁:ReentrantReadWriteLock

    作者:chenssy 来源:http://cmsblogs.com/?p=2213 重入锁ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都 ...

  7. 死磕Java并发:J.U.C之AQS:CLH同步队列

    本文转载自公号:Java技术驿站 在上篇文章"死磕Java并发:J.U.C之AQS简介"中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列. CLH同步队列是一个F ...

  8. 死磕Java并发:J.U.C之AQS简介

    本文转载自公众号: Java技术驿站 Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略(死磕J ...

  9. 【死磕Java并发】-----J.U.C之AQS:CLH同步队列

    原文出处:https://www.cmsblogs.com/category/1391296887813967872 『chenssy』 在上篇博客[死磕Java并发]-----J.U.C之AQS:A ...

最新文章

  1. Ext JS 4 笔记1
  2. SAP MM 物料库存转固定资产,报错:You cannot post to asset in company code 1900 fiscal year 2021
  3. 高校促进“智慧城市”信息化建设策略研究
  4. STM32F7xx —— 96位唯一ID
  5. sarscape 将dem文件转化成stl_SARscape与SARProz软件中的重要缩写
  6. hashmap扩容机制_图文并茂:HashMap经典详解!
  7. 帝豪gl车机系统降级_何以剑指合资?帝豪GL/英朗底盘对比
  8. 解决VC++6.0打开文件或添加文件到工程出错的问题
  9. 嵌入式LINUX内核开发专家谈“龙芯”签约MIPS
  10. MySQL 常用分库分表方案,都在这里了!
  11. 关于《JavaScript百炼成仙》电子版,在线阅读地址~
  12. Python对话框使用
  13. 万元怎样保留小数点_excel中数值转换成万元后如何只保留两位小数?,excle 万元 元...
  14. 解决vue中父组件传图片路径src给子组件却无法正常显示图片的原因
  15. 【产业互联网周报】外媒:英特尔等公司暂停向俄罗斯发货;阿里云季度营收195亿元;第四范式再次提交上市申请...
  16. java 二义性_Java接口默认方法带来的问题分析【二义性问题】
  17. vagrant启动虚拟机Stderr: VBoxManage.exe: error: Call to WHvSetupPartition failed: ERROR_SUCCESS (Last=0xc
  18. 推荐系统(十五)多任务学习:谷歌MMoE(Multi-gate Mixture-of-Experts )
  19. 趣学算法【第一章:算法之美】感悟(上)
  20. 项目实战一 12306火车票余票查询软件

热门文章

  1. linux docker 限制容器可用的 cpu
  2. postgresql 数据库 客户端认证
  3. python2 转 python3 代码
  4. linux “大脏牛”漏洞分析(CVE-2017-1000405)
  5. linux od命令详解
  6. golang fmt包Printf 格式化输出 简介
  7. Linux2.6内核--VFS层中和进程相关的数据结构
  8. 修改initrd做miniLinux
  9. 嵌入式系统实验 构建嵌入式Linux系统,《嵌入式系统与开发》构建嵌入式Linux系统-实验报告.doc...
  10. log4j linux如果日志目录不存在,Java日志库学习笔记