j.u.c系列(08)---之并发工具类:CountDownLatch

写在前面

  CountDownLatch所描述的是”在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待“:用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。CountDownLatch的本质也是一个"共享锁"

\

CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()

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

  虽然,CountDownlatch与CyclicBarrier(后续会接受。另外一并发工具类)区别:

  1. CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待
  1. CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier

 

实现分析

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

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

 

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

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

 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) {for (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}}

 

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

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

public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}

 

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

    public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

 

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

 protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}

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

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) {/*** 对于CountDownLatch而言,如果计数器值不等于0,那么r 会一直小于0*/int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//等待if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

 

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

public void countDown() {sync.releaseShared(1);}

 

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

 public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}

 

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

protected boolean tryReleaseShared(int releases) {for (;;) {//获取锁状态int c = getState();//c == 0 直接返回,释放锁成功if (c == 0)return false;//计算新“锁计数器”int nextc = c-1;//更新锁状态(计数器)if (compareAndSetState(c, nextc))return nextc == 0;}}

 

 

总结

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

应用示例

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

public class CountDownLatchTest {private volatile static CountDownLatch countDownLatch = new CountDownLatch(5);/*** Boss线程,等待员工到达开会*/static class BossThread extends Thread{BossThread(String name){super(name);}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ":Boss在会议室等待,总共有" + countDownLatch.getCount() + "个人开会...");try {//Boss等待
                countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":所有人都已经到齐了,开会吧...");}}//员工到达会议室static class EmpleoyeeThread  extends Thread{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + ",到达会议室....");//员工到达会议室 count - 1
            countDownLatch.countDown();}}public static void main(String[] args) throws InterruptedException{//Boss线程启动new BossThread("张总").start();new BossThread("李总").start();new BossThread("王总").start();Thread.sleep(1000);for(int i = 0 ; i < 5 ; i++){new EmpleoyeeThread().start();}}
}
张总:Boss在会议室等待,总共有5个人开会...
李总:Boss在会议室等待,总共有5个人开会...
王总:Boss在会议室等待,总共有5个人开会...
Thread-0,到达会议室....
Thread-1,到达会议室....
Thread-2,到达会议室....
Thread-3,到达会议室....
Thread-4,到达会议室....
张总:所有人都已经到齐了,开会吧...
王总:所有人都已经到齐了,开会吧...
李总:所有人都已经到齐了,开会吧...

 

转载于:https://www.cnblogs.com/chihirotan/p/8526692.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/466509.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

巧用1个GPIO控制2个LED显示4种状态

很多电子产品有状态指示灯&#xff0c;比如电视机&#xff1a;待机状态亮红灯开机状态亮绿灯实现起来很简单&#xff0c;微控制器MCU的两个GPIO分别控制就行&#xff1a;不过资源总是紧张的&#xff0c;有时候会碰到GPIO不够用的情况。如果只用1个GPIO&#xff0c;可不可以实现…

大大大大数怎么求余?C语言

问题&#xff1a;一个特别大的数除以23求余数用C语言应该怎么算啊&#xff1f;比如23232323232323232323232323232323232323232323232323232323233除以23&#xff0c;怎么算余数&#xff1f;数据类型在计算机的存储是有大小限制的&#xff0c;所以才出现了大数求余这种问题&…

程序员因拒绝带电脑回家工作被开除!获赔19.4万元

近日&#xff0c;男子拒绝春节带电脑回家工作被开除的消息&#xff0c;成为了不少网友关注的焦点&#xff0c;引发网友共鸣。因为春节拒绝带工作电脑回家被开除&#xff0c;上海一位软件工程师起诉公司获赔19.4万元。2月2日&#xff0c;据上海浦东法院公众号消息&#xff0c;该…

随便写写(5)

也许是今年发生的事情太多了&#xff0c;所以比以前要更关注时事&#xff0c;虽然面对一些既成的事实&#xff0c;难免要进行痛心的思考。 昨天晚上关注了一下东方卫视播出的9.8特大尾矿库溃坝事故的后续报道&#xff0c;这起特大人为事故已经得到了认定&#xff0c;相关的责任…

利用C语言中的setjmp和longjmp,来实现异常捕获和协程

一、前言二、函数语法介绍与 goto 语句比较与 fork 函数比较与 Python 语言中的 yield/resume 比较三、利用 setjmp/longjmp 实现异常捕获四、利用 setjmp/longjmp 实现协程五、总结一、前言 在 C 标准库中&#xff0c;有两个威力很猛的函数&#xff1a;setjmp 和 longjmp&…

centos6.9系列LNMP环境的安装

一、Nginx 1.先解决Nginx的依赖关系&#xff1a; yum install -y pcre-devel openssl-devel 2.安装wget&#xff1a;sudo yum -y install wget 3.下载nginx的安装包&#xff1a;wget http://nginx.org/download/nginx-1.10.3.tar.gz 4.解压nginx文件包&#xff1a;tar xf nginx…

Linux 修改 ELF 解决 glibc 兼容性问题

转自&#xff1a;Soul Of Free Loophttps://zohead.com/archives/mod-elf-glibc/Linux glibc 问题相信有不少 Linux 用户都碰到过运行第三方&#xff08;非系统自带软件源&#xff09;发布的程序时的 glibc 兼容性问题&#xff0c;这一般是由于当前 Linux 系统上的 GNU C 库&am…

VS2010创建ATL工程及使用C++测试COM组件

VS2010创建ATL工程及使用C测试COM组件 1.创建ATL项目&#xff0c;取名MyCom 2. ATL 项目向导&#xff0c;勾选 【支持COM 1.0】和【支持部件注册器】&#xff0c;其他默认&#xff0c;点击完成。 3.在该项目中添加类 4.添加一个ATL简单对象 5. ATL 简单对象向导&#xff0c…

芯片IC附近为啥要放0.1uF的电容?看完秒懂~

数字电路要运行稳定可靠&#xff0c;电源一定要”干净“&#xff0c;并且能量补充一定要及时&#xff0c;也就是滤波去耦一定要好。什么是滤波去耦&#xff0c;简单的说就是在芯片不需要电流的时候存储能量&#xff0c;在需要电流的时候又能及时地补充能量。有读者看到这里会说…

无线中继蹭网(转)

随着无线技术的逐渐成熟&#xff0c;无线设备的价格也越来越低&#xff0c;已经有不少的家庭开始在自己的家中建立无线网络&#xff0c;利用笔记本&#xff0c;具备WiFi功能的手机连接无线网络享受冲浪乐趣&#xff0c;很多时候为了节约网费可能几家人一起共用一个ADSL上网帐号…

深入掌握Linux操作系统,其实也没你想象那么难

曹政大家应该都不陌生吧&#xff0c;众多IT人的偶像&#xff0c;数据、技术、业务&#xff0c;无一不精&#xff0c;被大家称为曹大。在曹大的一篇文章中&#xff0c;他曾经提到过&#xff0c;1998年&#xff0c;自己的第一份工作接手的是一个Windows系统下的人才网站系统&…

WSS页面定制系列(1)--如何启用表单页面的编辑模式

wss的大多数页面右上角的“站点操作”菜单都有一个编辑网页菜单项&#xff0c;用这个菜单项&#xff0c;可以启用当前页面的设计模式&#xff0c;修改或添加webpart。但是奇怪的是&#xff0c;所有的表单页面&#xff08;用来新建&#xff0c;编辑&#xff0c;查看列表项的页面…

单片机检测220V交流电通断电路

我们在topemic网站上分享过一篇题为"单片机检测220V交流电通断电路"的文章&#xff0c;目前有近万次阅读&#xff0c;在这里做个总结分享给没有读过该文的公众号朋友。废话不多说&#xff0c;直接上图&#xff1a;该电路工作原理如下&#xff1a;当220V断开时&#x…

Qt值得学习吗?详解Qt的几种开发方式

qt值得学习吗&#xff1f;嵌入式要学的东西真的很多&#xff0c;我们可能会说不写界面的话就不用学qt了&#xff1f;我不赞同。Qt的实现主要是采用p-impl手法&#xff0c;实现接口与实现分离&#xff0c;它有很好的消息循环机制&#xff0c;有的对象与线程的相关性&#xff0c;…

技术QA:如何安装并启用BITS和WebDAV?

引子&#xff1a; 在安装SCCM 2007 SP1时&#xff0c;必须要安装并启用BITS和WebDAV&#xff0c;否则在SCCM 2007 SP1安装先决条件检查时将会报错。它们以前都是IIS的功能组件&#xff0c;但是在IIS 7中变动很大&#xff0c;特别是在 Windows Server 2008 操作系统中现在已经不…

C语言,谁都能看得懂的归并排序

喜欢看排序算法动态效果的&#xff0c;可以看看这个网站https://visualgo.net/zh/sorting里面很多算法的动画解释&#xff0c;可以看到算法的排序效果&#xff0c;而且还附带了伪代码的实现过程。本来想录制几张动图放上来&#xff0c;但是因为图片较大&#xff0c;传不上来&am…

内核链表list.h文件剖析

内核链表list.h文件剖析 一、内核链表的结构【双向循环链表】 内核链表的好主要体现为两点&#xff0c;1是可扩展性&#xff0c;2是封装。可以将内核链表复用到用户态编程中&#xff0c;以后在用户态下编程就不需要写一些关于链表的代码了&#xff0c;直接将内核中list.h中的代…

CAN总线很难吗?CAN总线看不懂是不可能的!

CAN&#xff08;Controller Area Network&#xff09;即控制器局域网&#xff0c;是一种能够实现分布式实时控制的串行通信网络。想到CAN就要想到德国的Bosch公司&#xff0c;因为CAN就是这个公司开发的&#xff08;和Intel&#xff09;CAN有很多优秀的特点&#xff0c;使得它能…

C语言必须写main函数?最简单的 Hello world 你其实一点都不懂!

我们在刚写程序的时候&#xff0c;第一个都是 hello world&#xff0c;而在这里&#xff0c;完整的代码就是&#xff1a;我们打眼一看&#xff0c;其实很简单&#xff0c;就是引入头文件&#xff0c;写一个主函数&#xff0c;然后输出一句话&#xff0c;但是当我们编译出来ELF的…

源码包安装

一、源码包和RPM包的区别 1、区别 安装之前的区别&#xff1a;概念上的区别 安装之后的区别&#xff1a;安装位置不同 2、RPM包安装位置 是安装在默认位置中 注&#xff1a;安装位置是写RPM包的作者决定的 注&#xff1a;RPM包支持指定安装位置&#xff0c;但是不建议指定位置安…