JUC 十三. CountDownLatch 与 CyclicBarrier 与 Semaphore 基础使用与底层原理

2023-11-01

一. CountDownLatch 减少计数器

  1. CountDownLatch 减少计数器,有点像倒计,计数器,当减少为0时线程执行
  2. 作用是允许1或者多个线程,等待另外N个线程完成某件事情之后,这1个或者多个线程才能执行。CyclicBarrier 是N个线程相互等待,任何一个线程完成任务之前,所有的线程必须等待。
  3. CountDownLatch 计数器是一次性的,无法被重置的,而CyclicBarrier的计数器在调用reset方法之后,还可以重新使用,因此被称为循环的barrier
public class T1 {
    public static void main(String[] args) throws InterruptedException {
        //1.创建一个 CountDownLatch 初始化计数为3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                //2.计数减1
                countDownLatch.countDown();
                System.out.println("线程执行计数减1");
            },String.valueOf(i)).start();
        }
        //3.判断CountDownLatch中的计数是否为0,如果不为0会阻塞,当为0时,会在此位置唤醒继续向下执行
        countDownLatch.await();
        //4.当CountDownLatch中的计数减为0时才会执行
        System.out.println("计数为0");
    }
}

二. CyclicBarrier 循环栅栏

  1. CyclicBarrier 循环栅栏与 CountDownLatch相反的计数器
	public static void main(String[] args){

        //1.创建一个实际工作线程,提供工作的run方法
        Thread myThread = new Thread(() -> {
            System.out.println("实际工作线程工作---->");
        }, "实际工作线程");
        //2.创建一个 CyclicBarrier 需要两个参数
        //第一个:指定计数器值
        //第二个:需要一个 Runnable 类型变量,当计数器值到达时自动执行 Runnable 工作方法
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, myThread);
        
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    System.out.println("计数器线程执行");
                    //3.当多线程执行时,会判断是否达到计数器设置的值
                    //如果没达到会阻塞,并且每执行一次累计加1,当达到时
                    //自动执行CyclicBarrier 构造器中传入的线程myThread工作方法
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }

三. Semaphore 信号灯

  1. Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数(当运行的并发访问数为1时,就有点像 synchronized)
  2. Semaphore的主要方法摘要:
  • void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
  • void release():释放一个许可,将其返回给信号量。
  • int availablePermits():返回此信号量中当前可用的许可数。
  • boolean hasQueuedThreads():查询是否有线程正在等待获取。
  1. 代码示例
	public static void main(String[] args) throws InterruptedException {
        //1.创建一个 Semaphore 初始化资源运行的最大并发量为3
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {

            new Thread(()->{
                try {

                    //2.当前线程执行抢占资源
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢占到了资源");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //3.线程执行完毕,释放资源
                    System.out.println(Thread.currentThread().getName()+"释放资源");
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }

四. CountDownLatch 底层实现

  1. 在使用CountDownLatch时首先要创建一个CountDownLatch对象,并初始化计数,了解CountDownLatch底层要在三个部分,调用构造器创建, 调用await()方法判断计数器是否为0,获取锁,如果获取成功则执行,否则阻塞,调用countDown()方法计数减一
  2. 查看ConutDownLatch构造器,与内部结构会发现CountDownLatch中有一个继承了AbstractQueuedSynchronizer 内部类Sync,整个CountDownLatch是基于AQS实现的,在AQS中有一个被volatile修饰的int类型属性state,通过通过state进行计数
  3. 唤醒与阻塞底层通过Unsafe 的 unpark()唤醒,park()阻塞实现的
	//构造器,调用构造器创建时,内部会判断计数器是否大于0,会创建一个Sync对象(AQS)
	public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

	//阻塞方法,会执行AQS中的acquireSharedInterruptibly方法
	public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

	//1.该方法其实调用AQS中的releaseShared(1)释放共享锁方法。
	public void countDown() {
	    sync.releaseShared(1);
	} 

await() 判断state,也可以简单理解为计数,如果为0获取锁成功,当前线程执行,否则获取锁失败,阻塞当前线程

  1. 调用await()方法,内部调用的是AQS的acquireSharedInterruptibly()方法,执行tryAcquireShared(arg)获取AQS中state的值是否为0,如果是0说明没没有被其它线程获取大锁,直接放行,否则执行doAcquireSharedInterruptibly()
	//1.获取共享锁
 	public final void acquireSharedInterruptibly(int arg) throws InterruptedException {      
      //判断线程是否为中断状态,如果是抛出interruptedException
      if (Thread.interrupted())
            throw new InterruptedException();
        //尝试获取共享锁,尝试成功就返回,否则调用doAcquireSharedInterruptibly方法
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
	//2.尝试获取共享锁,重写AQS里面的方法
	protected int tryAcquireShared(int acquires) {
	    //锁状态 == 0,表示所没有被任何线程所获取,即是可获取的状态,否则锁是不可获取的状态
	    return (getState() == 0) ? 1 : -1;
	}
  1. doAcquireSharedInterruptibly方法会使得当前线程一直等待,直到当前线程获取到锁(或被中断)才返回,该方法中会创建一个存储当前线程的Node节点,并将节点添加到CLH队列末尾,
  2. 判断当前节点的上一个节点在AQS中是否是AQS的头节点,如果是,说明正在执行的线程如果执行完毕,是否锁后,下一个执行的线程就是当前节点持有的线程,会调用tryAcquireShared(arg)方法再次尝试获取锁,减少不必要的线程阻塞与唤醒
  3. 如果获取锁失败,或者当前node节点的上一个节点不是AQS的头节点,执行shouldParkAfterFailedAcquire(p, node)方法,根据当前当前节点的上一个节点状态,维护自身的状态
	//3.doAcquireSharedInterruptibly方法会使得当前线程一直等待,直到当前线程获取到锁(或被中断)才返回
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //创建“当前线程”的Node节点,且node中记录的锁是“共享锁”类型,并将节点添加到CLH队列末尾。
        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);
        }
    }
	//4.通过以下规则,判断“当前线程”是否需要被阻塞,此处规则中的状态是AQS中的Node节点的状态
	//实际该方法就是通过前继节点的状态维护自身的状态
	//规则1:如果前继节点状态为SIGNAL,表明当前节点需要被unpark(唤醒),此时则返回true。
    //规则2:如果前继节点状态为CANCELLED(ws>0),说明前继节点已经被取消,则通过先前回溯找到一个有效(非CANCELLED状态)的节点,并返回false。
    //规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,并返回false
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	        // 前驱节点的状态
	        int ws = pred.waitStatus;
	        // 如果前驱节点是SIGNAL状态,则意味着当前线程需要unpark唤醒,此时返回true
	        if (ws == Node.SIGNAL)
	       		return true;
	        // 如果前继节点是取消的状态,则设置当前节点的“当前前继节点为”原节点的前继节点
	        if (ws > 0) {
	            do {
	                node.prev = pred = pred.prev;
	            } while (pred.waitStatus > 0);
	            pred.next = node;
	        } else {
	            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	        }
	        return false;
	    }

countDown()

//2.目的是让当前线程释放它所持有的共享锁,它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
//3.tryReleaseShared()在CountDownLatch.java中被重写,释放共享锁,将锁计数器-1
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        // 获取“锁计数器”的状态
        int c = getState();
        if (c == 0)
            return false;
        // “锁计数器”-1
        int nextc = c-1;
        // 通过CAS函数进行赋值。
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JUC 十三. CountDownLatch 与 CyclicBarrier 与 Semaphore 基础使用与底层原理 的相关文章

随机推荐

  • 5分钟带你快速了解微服务框架的前世今生

    目录 原始时代 青铜时代 黄金时代 铂金时代 钻石时代 星耀时代 王者时代 总结 原始时代 1969年11月 为了便于高校间共享资源 美国国防部高级研究计划管理局建立一个名为阿帕网络ARPAnet 起初只有四个节点 阿帕网起源 一年后阿帕网
  • 编程是一种“组合的艺术”

    编程是一种 组合的艺术 WPF实例分析 金旭亮 有这么一句名言 政治是一种妥协的艺术 这一规律同样适用于软件技术 就我个人的观点 软件开发在一定意义上是一种 组合的艺术 优秀的软件工程师类似于优秀的厨师 能将一些常见的原料变成一盘色香味俱全
  • git常用命令合集(建议收藏)

    1 git init将本文件夹初始化成一个本地git仓库 2 git clone xxx 将github上的远程克隆到本地 3 git add file1 file2 添加文件到暂存区 包括修改的文件 新增的文件 4 git add dir
  • 技术人修炼之道阅读笔记(四)低效的7个行为习惯

    技术人修炼之道阅读笔记 四 低效的7个行为习惯 如今 在许多企业为了提高团队的产出 996 非常盛行 但是效果却往往不令人满意 那么怎么来提高团队的工作效率呢 这里我们进行逆向思维 从造成低效的7个坏习惯说起 因为避免了低效的行为习惯 离高
  • 无限脉冲响应 (IIR) 滤波器

    1 基础知识 某正弦信号 频率为50Hz 这意味着 信号的模拟频率 f 50 Hz 注意它的单位是Hz 信号的表达式为 注意 模拟滤波器设计中用的频率是指模拟角频率 数字滤波器设计中用的频率是指归一化数字角频率 w 2 数字滤波器简介 数字
  • 程序员应对35岁中年危机的措施

    都说程序员是吃青春饭 我也问了一些身边周围的朋友 有已经在工作的 有正在出于中年危机的 有猎头公司工作的 觉得年龄问题对于程序员是一个致命的问题 正处在25左右的我们 应该如何应对10年后的中年危机呢 李开复给程序员的7建议 我觉得很对 特
  • 第二章正则表达式

    第二章 正则表达式 1 学习目标 掌握正则表达式的作用 掌握正则表达式的语法 了解常见的正则表达式 2 内容讲解 2 1 正则表达式的概念 正则表达式是对字符串操作的一种逻辑公式 就是用事先定义好的一些特定字符 及这些特定字符的组合 组成一
  • c中malloc申请堆空间使用及案例

    c中malloc申请堆空间 void test22 int pr pr int malloc sizeof int 128 申请128个int4字节空间 if pr NULL 判断是否申请成功 return memset pr 0 size
  • elasticsearch字符串包含查询

    query string 在查询的时候经常会遇到查询字符串是否包含的某个特定字符传的查询 可以使用query string实现 GET pv search query query string default field name quer
  • NodeJS-进程管理pm2/forever/nodemon/supervisor/ts-node-dev

    无论在开发阶段还是在上线阶段 对进程的管理是大大解决时间和成本的 pm2 node js server tools 1 安装 全局安装 yarn global add pm2 OR npm install pm2 g 局部安装 yarn a
  • springcloud报错:com.netflix.discovery.shared.transport.TransportException

    1 完整报错信息 com netflix discovery shared transport TransportException Cannot execute request on any known server 2 问题分析 1 服
  • 图片风格快速转换的简单web实现

    图片风格快速转换的简单web实现 图片风格转换 是指利用深度学习算法学习某种风格图片的特征 将其应用到另一张图片中 合成新风格的图片 目前技术较为成熟 github上有很多有趣的项目与应用 本项目核心代码基于fast neural styl
  • NCC常用操作自助工具

    selftool 介绍 NChome操作自助工具 对常用操作进行了集成 类图 工具使用 功能介绍 初始化配置 点击按钮可对nchome进行关联 重启服务 点击按钮一键重启服务 sysConfig 点击按钮一键打开home配置界面 clear
  • 云计算技术与应用赛项竞赛试题(样卷)

    选手须知 1 竞赛试题通过在线 云计算技术与应用 竞赛考试系统和书面文档共同发布 内容完全一致 如出现纸质任务书缺页 字迹不清 与考试系统中不一致等问题 请及时向裁判示意 并进行任务书的更换 2 参赛团队应在4小时内完成任务书规定内容 选手
  • 尝试实现带有迭代器的 vector

    两个注意点 1 底层内存分配采用 new 和 delete 非 stl 书中所示 2 移动元素为集体后移 并非原书中拆解后移 原书移动方式原因未知 include
  • docker部署多个mysql容器,并使用java连接

    测试springboot多个数据源配置时 需要安装多个mysql容器 由于资源限制 当前只有一台虚拟机 如果在一台机器上安装多个mysql实例 是可以的 但步骤比较繁琐 使用docker来安装MySQL容器 非常简单 只需要简单几步 对于测
  • vue创建WebSocket进行实时通讯

    vue WebSocket创建实现实时通讯 websocket async initWebSocket if websock return if const chatid return if typeof WebSocket undefin
  • C++标准库头文件(工具库->typeinfo)

    参考网址 https zh cppreference com w cpp header https blog csdn net w55100 article details 80330812 工具库 typeinfo 运行时类型信息工具 类
  • 3. Unity之三维模型

    1 网格 Mesh 三维物体模型在unity中一般称为mesh 即网格数据 模型一般使用专用的建模软件设计 将mesh文件导入到unity中进行使用 一般mesh中保存的是三维模型的面和顶点数据 在unity中通过下图方法进行调整 其中 S
  • JUC 十三. CountDownLatch 与 CyclicBarrier 与 Semaphore 基础使用与底层原理

    目录 一 CountDownLatch 减少计数器 二 CyclicBarrier 循环栅栏 三 Semaphore 信号灯 四 CountDownLatch 底层实现 await 判断state 也可以简单理解为计数 如果为0获取锁成功