CountDownLatch、CyclicBarrier 和 Semaphore

文章目录

  • 一、CountDownLatch
    • 1、实现原理
    • 2、使用场景
    • 3、代码
  • 二、CyclicBarrier
    • 1、实现原理
    • 2、使用场景
    • 3、代码
    • 4、CountDownLatch与CyclicBarrier区别
  • 三、Semaphore
    • 1、实现原理
    • 2、使用场景
    • 3、代码
  • 四、总结

一、CountDownLatch

CountDownLatch计数器

1、实现原理

主要基于计数器和阻塞队列。

CountDownLatch 内部维护一个计数器,这个计数器的初始值通常设置为需要等待的线程数量。当一个线程调用 CountDownLatch 的 await() 方法时,如果计数器的值大于 0,则该线程会被放入一个阻塞队列中等待,并处于挂起状态。每当一个线程完成了自己的任务后,它会调用 CountDownLatch 的 countDown() 方法,使计数器递减。当计数器的值递减到 0 时,CountDownLatch 会唤醒阻塞队列中所有等待的线程,使它们能够继续执行后续的任务。

2、使用场景

用于等待多个线程完成后进行指定操作。

常见场景:

  • 服务启动时要等待多个资源初始化
  • 并行任务处理,有多个并行处理的任务,并且需要在任务都处理完毕后,再做其他处理。比如:并行计算成绩,最终汇总分数;分开去多个服务查询前置数据,然后进行校验
  • 模拟高并发测试,在测试一个多线程并发访问的共享资源时,可以使用 CountDownLatch 来确保所有线程都准备好访问共享资源后再进行实际测试。
  • 异步编程中的等待机制,等待某个异步操作完成后才继续执行后续代码

3、代码

伪代码如下:

// 初始化 CountDownLatch,计数器设为 N  
CountDownLatch latch = new CountDownLatch(N);  
  
// 在 N 个线程中  
for (int i = 0; i < N; i++) {  
    new Thread(() -> {  
        // 执行一些任务  
        // ...  
  
        // 任务完成后,计数器减一  
        latch.countDown();  
    }).start();  
}  
  
// 在主线程中等待所有线程完成任务  
latch.await(); // 阻塞直到计数器为0  
  
// 所有任务都已完成,继续执行后续代码  
// ...

案例:主要模拟3个任务并行,然后主线程阻塞等待

static ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
	// 多个任务并发执行,都执行完毕后,再执行主线程,也就是await的线程
	System.out.println("主业务开始执行");
	sleep(1000);
	executor.execute(CountDownLatchTest::a);
	executor.execute(CountDownLatchTest::b);
	executor.execute(CountDownLatchTest::c);
	System.out.println("三个任务并行执行,主业务线程等待");
	// 死等任务结束
	// countDownLatch.await();
	// 如果在规定时间内,任务没有结束,返回false
	if (countDownLatch.await(2, TimeUnit.SECONDS)) {
		System.out.println("三个任务处理完毕,主业务线程继续执行");
	} else {
		System.out.println("三个任务没有全部处理完毕,执行其他的操作");
	}
}

private static void a() {
	System.out.println("A任务开始");
	sleep(3000);
	System.out.println("A任务结束");
	countDownLatch.countDown();
}
private static void b() {
	System.out.println("B任务开始");
	sleep(1500);
	System.out.println("B任务结束");
	countDownLatch.countDown();
}
private static void c() {
	System.out.println("C任务开始");
	sleep(2000);
	System.out.println("C任务结束");
	countDownLatch.countDown();
}
private static void sleep(long timeout) {
	try {
		Thread.sleep(timeout);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

其中await()是死等任务结束,不限制时间;await(long timeout, TimeUnit unit)是在规定时间内任务没有结束,就返回false

二、CyclicBarrier

CyclicBarrier栅栏

1、实现原理

主要基于计数器、等待队列、循环栅栏。

CyclicBarrier 内部维护一个计数器,用于记录当前到达屏障点的线程数量,就是我们创建时指定的线程数。当一个线程到达屏障点时,如果计数器的值大于 0,则该线程会被放入一个等待队列中等待,并处于挂起状态,如果计数器的值变为 0,则说明所有线程都已到达屏障点,直接唤醒等待队列中的所有线程,并继续执行后续任务。在 CyclicBarrier 的构造函数中,可以指定一个可选的栅栏动作。当所有线程都到达屏障点时,这个栅栏动作会被执行一次,然后重置回初始状态并再次使用。

  • Barrier屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当一个线程到达屏障点时,就会对屏障点的数值进行-1操作,当屏障点数值减为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后的任务。基于ReentrantLock锁的await方法阻塞在屏障点。
  • Cyclic循环:所有线程被释放后,屏障点的数值可以再次被重置。

2、使用场景

用于让一组线程在某个屏障点相互等待,直到所有线程都到达该屏障点,然后它们才能继续执行。

常见场景:

  • 将任务分解成多个阶段,每个阶段由一组线程执行,并且需要在所有阶段完成后才能继续下一个阶段,比如游戏中所有人到达终点,才开启下一关

3、代码

伪代码如下:

// 初始化 CyclicBarrier,参与线程数为 N,可选的屏障动作(barrierAction)  
CyclicBarrier barrier = new CyclicBarrier(N, () -> {  
    // 所有线程到达屏障点时执行的代码  
    // ...  
});  
  
// 在 N 个线程中  
for (int i = 0; i < N; i++) {  
    new Thread(() -> {  
        // 执行一些任务  
        // ...  
  
        // 到达屏障点,等待其他线程  
        barrier.await(); // 阻塞直到所有线程到达  
  
        // 所有线程都已到达屏障点,继续执行后续代码  
        // ...  
    }).start();  
}

案例:大家集合完毕后,再一起出发

CyclicBarrier barrier = new CyclicBarrier(3,() -> {
	System.out.println("各位大佬集合完毕,发护照准备出发!");
});
new Thread(() -> {
	System.out.println("Tom到位!!!");
	try {
		barrier.await();
	} catch (Exception e) {
		System.out.println("悲剧,人没到齐!");
		return;
	}
	System.out.println("Tom出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {
	System.out.println("Jack到位!!!");
	try {
		barrier.await();
	} catch (Exception e) {
		System.out.println("悲剧,人没到齐!");
		return;
	}
	System.out.println("Jack出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {
	System.out.println("Rose到位!!!");
	try {
		barrier.await();
	} catch (Exception e) {
		System.out.println("悲剧,人没到齐!");
		return;
	}
	System.out.println("Rose出发!!!");
}).start();

4、CountDownLatch与CyclicBarrier区别

  • 底层实现不同:CountDownLatch基于AQS。CyclicBarrier基于ReentrantLock。
  • 应用场景不同:CountDownLatch的计数器只能使用一次。而CyclicBarrier在计数器达到0之后,可以重置计数器,可以实现相比CountDownLatch更复杂的业务,如果执行业务时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
  • 等待对象不同:CountDownLatch一般是让主线程等待,让子线程对计数器–。CyclicBarrier更多的让子线程也一起计数和等待,等待的线程达到数值后,再统一唤醒

三、Semaphore

Semaphore(信号量),保证x个资源可以被多个线程同时访问

1、实现原理

Semaphore底层也是基于AQS的state属性做一个计数器的维护。state的值就代表当前共享资源的个数。如果一个线程需要获取的x个资源,
直接查看state的标识的资源个数是否足够,如果足够的,直接对state-x拿到当前资源。如果资源不够,当前线程就需要挂起等待。
知道持有资源的线程释放资源后,会归还给Semaphore中的state属性,挂起的线程就可以被唤醒。

2、使用场景

用于控制对共享资源的并发访问数量。它维护了一个可用的许可证数量,并允许线程通过获取(acquire)和释放(release)许可证来访问资源。当没有可用许可证时,线程会等待。

常见场景:

  • 数据库连接池管理,限制同时访问数据库连接的线程数量
  • 线程池管理,限制同时执行的线程数量
  • 实现互斥锁,Semaphore的初始值设置为1,确保同一时间只能有一个线程可以访问
  • 流量控制,平衡系统的负载和资源利用

3、代码

伪代码如下:

// 初始化 Semaphore,允许同时访问的线程数为 M  
Semaphore semaphore = new Semaphore(M);  
  
// 在多个线程中  
for (int i = 0; i < 任意数量; i++) {  
    new Thread(() -> {  
        // 请求一个许可  
        semaphore.acquire(); // 阻塞直到有一个许可可用  
  
        try {  
            // 进入临界区,执行受保护的代码  
            // ...  
  
            // 临界区结束  
        } finally {  
            // 释放一个许可  
            semaphore.release();  
        }  
  
        // 继续执行其他代码  
        // ...  
    }).start();  
}

案例:环球影城,每天接受的人流量是固定的,每有一个人购票后,就对信号量进行–操作,如果信号量已经达到了0,或者是资源不足,此时就不能买票。

Semaphore semaphore = new Semaphore(10);
new Thread(() -> {
	System.out.println("一家三口来了");
	try {
		semaphore.acquire(3);
		System.out.println("一家三口进去了~~~");
		Thread.sleep(10000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		System.out.println("一家三口走了~~~");
		semaphore.release(3);
	}
}).start();
for (int i = 0; i < 7; i++) {
	int j = i;
	new Thread(() -> {
		System.out.println(j + "大哥来了");
		try {
			semaphore.acquire();
			System.out.println(j + "大哥进去了~~~");
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			System.out.println(j + "大哥走了~~~");
			semaphore.release();
		}
	}).start();
}
Thread.sleep(2000);
System.out.println("main大哥来了");
if (semaphore.tryAcquire()) {
	System.out.println("main大哥进去了~~~");
} else {
	System.out.println("资源不够,main大哥停止进去");
}
Thread.sleep(3000);
System.out.println("main大哥又来了");
if (semaphore.tryAcquire()) {
	System.out.println("main大哥进去了~~~");
	semaphore.release();
} else {
	System.out.println("资源不够,main大哥停止进去");
}

四、总结

总的来说,CountDownLatch、CyclicBarrier 和 Semaphore 是 JVM 级别的同步工具,它们的状态是存储在JVM 的内存中的,主要用于单个 JVM 进程内的线程同步和协作,主要用于解决多线程编程中的一些问题,例如等待多个线程完成某些任务、让一组线程在某个点同步继续执行,或者限制对共享资源的并发访问数量。而在分布式场景下并不直接适用,可以考虑其它解决方案来实现类似的功能,比如:分布式锁、数据库事务、外部存储系统等

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用QLoRA在自定义数据集上finetuning 大模型 LLAMA3 的数据比对分析

概述: 大型语言模型(LLM)展示了先进的功能和复杂的解决方案,使自然语言处理领域发生了革命性的变化。这些模型经过广泛的文本数据集训练,在文本生成、翻译、摘要和问答等任务中表现出色。尽管LLM具有强大的功能,但它可能并不总是与特定的任务或领域保持一致。 什么是LL…

探索全新商业模式:循环购的奥秘

你是否曾经遇到过这样的疑问&#xff1a;为何有的商家会推出“消费1000送2000”的优惠活动&#xff1f;每天还有钱可以领取&#xff0c;甚至还能提现&#xff1f;这背后究竟隐藏着怎样的商业逻辑&#xff1f;今天&#xff0c;作为你们的私域电商顾问&#xff0c;我将带大家深入…

【C++】继承 — 继承的引入、赋值切片详细讲解

前言 我们知道C语言是一门面向对象编程的语言&#xff0c;而面向对象编程有三大特性&#xff0c;它们分别是&#xff1a; 封装继承多态 目录 1. 继承的概念及定义1.1继承的概念1.2继承的定义格式1.3 继承的使用 2 基类和派生类对象赋值转换3 继承中的作用域3.1 派生类对象的存…

STM32使用L9110驱动电机自制小风扇

1.1 介绍&#xff1a; 该电机控制模块采用L9110电机控制芯片。该芯片具有两个TTL/CMOS兼容输入端子&#xff0c;并具有抗干扰特性&#xff1a;具有高电流驱动能力&#xff0c;两个输出端子可直接驱动直流电机&#xff0c;每个输出端口可提供750800mA动态电流&#xff0c;其峰值…

汽车行业芯片 车规级芯片 单车芯片( soc mcu)数量

链接&#xff1a;https://xueqiu.com/3000217281/272114755 10大车规级MCU芯片10大车规级MCU芯片 汽车芯片是什么&#xff1f; 汽车芯片即车规级芯片&#xff0c;标准要高于工业级和民用级芯片&#xff0c;仅次于军工级芯片。芯片大概有以下四种级别&#xff0c;分别是军工级…

Django关于ORM的增删改查

Django中使用orm进行数据库的管理&#xff0c;主要包括以下步骤 1、创建model&#xff0c; 2、进行迁移 3、在视图函数中使用 以下的内容可以先从查询开始看&#xff0c;这样更容易理解后面删除部分代码 主要包括几下几种&#xff1a; 1、增 1&#xff09;实例例化model,代…

js逆向,参数加密js混淆

关键词 JS 混淆、源码乱码、参数动态加密 逆向目标 题目1&#xff1a;抓取所有&#xff08;5页&#xff09;机票的价格&#xff0c;并计算所有机票价格的平均值&#xff0c;填入答案。 目标网址&#xff1a;https://match.yuanrenxue.cn/match/1目标接口&#xff1a;https://ma…

buuctf-misc题目练习二

ningen 打开题目后是一张图片&#xff0c;放进winhex里面 发现PK&#xff0c;PK是压缩包ZIP 文件的文件头&#xff0c;下一步是想办法进行分离 Foremost可以依据文件内的文件头和文件尾对一个文件进行分离&#xff0c;或者识别当前的文件是什么文件。比如拓展名被删除、被附加…

Nacos Docker 快速部署----解决nacos鉴权漏洞问题

Nacos Docker 快速部署 1. 说明 1.1 官方文档 官方地址 https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html docker启动文件的gitlhub地址 https://github.com/nacos-group/nacos-docker.git 问题&#xff1a; 缺少部分必要配置与说明 1.2 部署最新版本Nacos&…

【Linux调试器】:gdb的使用(常见指令)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux调试器gdb的使用&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

数据结构与算法之树和二叉树--树和二叉树的一些性质

目录 前言 一、树的定义 二、树的若干术语 1.结点的度 2.叶子 3.双亲与孩子 4.兄弟 5.祖先 6.树的度 7.结点的层次 8.树的深度 9.有序树和无序树 10.森林 三、树的逻辑结构 四、树的存储结构 1.顺序存储 2.链式存储 五、二叉树 1.定义 2.二叉树的五种状态 …

PPT职场课:话术+技巧+框架+案例,告别只会念PPT不会讲(8节课)

课程目录 001-讲PPT如何开场及导入?5个简单实用的方法.mp4 002-讲PPT如何过渡衔接结尾?6类话术争来就用.mp4 003-掌握这3个逻辑表达万能框架&#xff0c;搞定98的PPT.mp4 004-学会这3种PPT结构讲解技巧告别只会念不会讲(上).mp4 005-学会这3种PPT结构讲解技巧告别只会念…

关于如何取消数据请求的操作

直接上码&#xff1a; class RequestManager {constructor() {this.requestQueue []}addRequestQueue(axios) {// 创建取消令牌const cancelToken axios.CancelToken.source()this.requestQueue.push(cancelToken.cancel)return cancelToken.token}clearRequestQueue() {thi…

【半夜学习MySQL】数据库概念详解探索数据库到底是如何存储的?

&#x1f3e0;关于专栏&#xff1a;半夜学习MySQL专栏用于记录MySQL数据相关内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 什么是数据库主流数据库与数据库分类数据库的基本使用数据库的启动及关闭查看配置文件与数据库存储位置连接数据库服务器服务…

微型显示器可以实时监测大脑活动

美国团队开发基于LED的设备&#xff0c;以可视化大脑活动&#xff0c;在脑外科手术中指导神经外科医生 来自加州大学圣地亚哥分校和马萨诸塞州总医院的工程师和医生开发了一种薄膜显示设备&#xff0c;该设备结合了电极网格和特殊的GaN LED&#xff0c;可以在手术过程中实时跟…

5月9日作业

1&#xff0c;创建一对父子进程&#xff1a;父进程负责向文件中写入 长方形的长和宽子进程负责读取文件中的长宽信息后&#xff0c;计算长方形的面积。 1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #…

中国4月进口以美元计同比增长8.4%,出口同比增长1.5%

中国按美元计4月进出口同比增速均转负为正&#xff0c;双双超预期。 5月9日周四&#xff0c;海关总署公布数据显示&#xff0c;以美元计价&#xff0c;中国2024年4月进口同比增长8.4%至2201亿美元&#xff0c;前值同比下降1.9%&#xff0c;出口同比增长1.5%至2924.5亿美元&…

基于Spring Boot的公司OA系统设计与实现

基于Spring Boot的银行OA系统设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 用户登录界面&#xff0c;在银行OA系统运行后&#x…

ThingsBoard如何接受设备通过TCP发送的报文

1、概述 2、案例 2.1、阐述 2.2、导入依赖 2.3、构建Netty服务链接&#xff0c;接受的端口为8092 2.4、对数据进行相应的处理发送到ThingsBoard客户端 2.5、通过TCP链接工具 ​2.6、查看遥测数据 1、概述 TCP&#xff08;Transmission Control Protocol&#xff0c;传输…

【备战软考(嵌入式系统设计师)】11 - 硬件电路基础

逻辑门电路 首先我们需要先了解三个最基础的门电路&#xff0c;可以说我们一切的电子产品的基石就是这哥仨&#xff0c;它们就与&#xff0c;或&#xff0c;非。 与门和或门有两个输入端&#xff0c;一个输出端&#xff1b;非门有一个输入端一个输出端。 在我们数字电路中&a…
最新文章