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;可不可以实现…

GetTickcount函数

GetTickCount是一种函数。GetTickCount返回&#xff08;retrieve&#xff09;从操作系统启动所经过&#xff08;elapsed&#xff09;的毫秒数&#xff0c;它的返回值是DWORD。 GetTickcount函数&#xff1a;它返回从操作系统启动到当前所经过的毫秒数&#xff0c;常常用来判断某…

网络大小端转换函数

网络大小端转换函数 //***************************************************************************** // // htonl/ntohl - big endian/little endian byte swapping macros for // 32-bit (long) values // //**********************************************************…

5-全排列总结:

https://www.nowcoder.com/acm/contest/76/H 给一道题&#xff0c;可以去测试代码。 这里总结一下全排列的几种方法&#xff1a; 方法一&#xff1a;利用交换排列&#xff1a;缺点&#xff1a;不能按字典序排列&#xff0c;但可以借助set处理。 #include <bits/stdc.h> …

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

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

substr

substr &#xff08;C语言函数&#xff09; 编辑 substr是C语言函数&#xff0c;主要功能是复制子字符串&#xff0c;要求从指定位置开始&#xff0c;并具有指定的长度。如果没有指定长度_Count或_Count_Off超出了源字符串的长度&#xff0c;则子字符串将延续到源字符串的结尾…

***站长自述挂马经历 提醒挂马者回头是岸

我做站都已经接近三年了&#xff0c;期间像很多人一样买过很多玉米&#xff0c;但是因为养不起&#xff0c;至今只保留了一个域名&#xff08;159e.cn&#xff09; &#xff0c;当时学校正流行移动159的号码&#xff0c;然后e在网络上代表很多意思&#xff0c;就注册了这个域名…

微信公众号--相关资料

相关资料 l 官方文档&#xff1a;https://mp.weixin.qq.com/wiki?tresource/res_main&idmp1445241432 l 测试号&#xff1a;https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?actionshowinfo&tsandbox/index l 接口调试地址&#xff1a;https://mp.weixin.qq.…

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

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

键值键名

在注册表中 所谓的键&#xff0c;是指一个注册表条目。 键名&#xff0c;是这个条目的名称 键值是为这个条目所赋予的值。 比如&#xff1a; NoDesktop1 这就是一个键 NoDesktop是一个键名 1就是一个键值 这个条目的意思是说&#xff1a;NoDesktop是没有桌面当值为1的时候&…

随便写写(5)

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

STM32 LED灯的另一种写法

STM32 LED灯的另一种写法 #ifndef __BSP_LED_ #define __BSP_LED_#include <MM32x103.h> // 这个换成STM32的库文件就行 #include "type.h"// #define LED1_RUN_GRP GPIOC #define LED1_RUN_IDX GPIO_Pin_6 #define LED1_RUN_OFF() GPIO_ResetBit…

C# 获取枚举的描述属性

https://www.cnblogs.com/TanSea/p/6923743.html 转载于:https://www.cnblogs.com/niuniu0108/p/8532161.html

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

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

VS2010

https://jingyan.baidu.com/article/d5a880eb74824013f147cca4.html

1、如何理解SQL Server的实例

在项目实施过程中&#xff0c;不少用户会有这样的需求&#xff1a;要求开发一套基于SQL Server的新系统&#xff0c;这套系统验收通过后&#xff0c;要和一个原有的SQL Server系统合并&#xff0c;共用一个服务器&#xff0c;所以不能为新系统提供单独的服务器&#xff08;资金…

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…

MDK临界区

Keil临界区/********************************************************************************************************* ** Function name: __ENTER_CIRTICAL, __EXIT_CIRTICAL ** Descriptions: 临界区代码保护宏 ** input parameters: 返回…

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…