多线程控制并发数目工具类Semaphore
文章目录
- 前言
- Semaphore原理
- Semaphore源码解析
- 内部继承AQS保证同步
- acquire获取许可
- release释放许可
- 实战演示
- 总结
前言
在多线程编码过程中,我们会用到多线程来提升运行效率。比如我们的Executors创建线程池,程序尽可能的压榨CPU资源来提升我们程序吞吐量。但是过度的使用线程,也会将我们CPU资源榨干,从而让我们系统不能正常的提供服务。故今天我们引入JUC并发包下面的semaphore并发类,该类可以同时允许定量线程执行从而达到控制并发的目的。
Semaphore原理
Semaphore并发类提供了两个核心方法:acquire()方法和release()方法。acquire()方法表示获取一个许可,如果没有则等待,release()方法则是释放对应的许可。Semaphore维护了当前访问的个数,通过提供同步机制来控制同时访问的个数。
Semaphore源码解析
内部继承AQS保证同步
Semaphore 与AQS关系图:
进入java.util.concurrent 包下 Semaphore 并发类查看源码:
//继承AQS同步阻塞队列来实现同步功能
abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 1192457210091910933L;Sync(int permits) {setState(permits);}final int getPermits() {return getState();}final int nonfairTryAcquireShared(int acquires) {for (;;) {int available = getState();int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}
如上所示,首先Semaphore并发内部类继承AbstractQueuedSynchronizer 同步阻塞类,所以可以得出Semaphore是通过AQS机制来保证同步。AQS不明白的同学可以看我之前的博文,大概就是内部一个state状态,获取到资源 state 就加一,释放资源 state 就减一,当 state == 0 的时候表示阻塞队列中的其他线程可以获取该资源。
继续查看源码:
/*** Creates a {@code Semaphore} with the given number of* permits and nonfair fairness setting.** @param permits the initial number of permits available.* This value may be negative, in which case releases* must occur before any acquires will be granted.*/
public Semaphore(int permits) {sync = new NonfairSync(permits);
}/*** Creates a {@code Semaphore} with the given number of* permits and the given fairness setting.** @param permits the initial number of permits available.* This value may be negative, in which case releases* must occur before any acquires will be granted.* @param fair {@code true} if this semaphore will guarantee* first-in first-out granting of permits under contention,* else {@code false}*/
public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
如源码所示,Semaphore构造方法默认提供非公平锁同步构造方法,也提供了一个用户自定义是否公平锁的构造方法。是否公平锁同步直接影响阻塞队列中的哪个线程可以获取资源,公平锁是按照阻塞顺序获取资源,非公平锁是多个线程争抢资源。当然构造方法必须传入并发许可的总数,这个总数直接影响后续我们可以同时获取多少个许可。
acquire获取许可
查看Semaphore 获取许可的源码:
//是否可以获取许可
public boolean tryAcquire() {return sync.nonfairTryAcquireShared(1) >= 0;
}
//是否可以获取许可,并提供超时获取机制
public boolean tryAcquire(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//是否可以获取多个许可,并提供超时获取机制
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)throws InterruptedException {if (permits < 0) throw new IllegalArgumentException();return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
//提供一个无参获取许可方法,默认获取一个许可
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}//不间断的获取许可,如果获取不到就阻塞
public void acquireUninterruptibly() {sync.acquireShared(1);
}
//不间断的获取多个许可,如果获取不到就阻塞
public void acquireUninterruptibly(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.acquireShared(permits);
}
如源码所示,我获取许可有很多的方法。有是否可以获取许可,超时是否可以获取许可。当然还有获取许可的方法,以及阻塞获取许可的多个方法,这些方法本质上都是调用父AQS中的获取资源许可的方法,同学们可以选择自己适用的方法进行调用。
release释放许可
查看Semaphore 释放许可的源码:
//默认释放一个许可
public void release() {sync.releaseShared(1);
}
//同时释放多个许可
public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);
}
//调用父AQS的释放资源的方法
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}
如源码所示,Semaphore提供了多个释放许可的方法,我们可以根据实际需要选择释放许可的方法。
值得注意的是release() 方法调用了自己内部类Sync的释放资源方法,而Sync又是继承AQS阻塞队列并调用了父类doReleaseShared() 释放资源的方法。
实战演示
上面博文我们介绍了Semaphore并发类是一个同步类,它继承了AQS阻塞队列。Semaphore主要提供了acquire() 获取许可与release() 释放许可的方法,通过这两个方法我们可以实现线程并发数目的功能。下面我们简单模拟一下限制并发数目。
1、Semaphore测试类
测试类提供一个定量5的线程池,用countdownlatch 让主线程等待子线程执行完成,Semaphore保证每次只有3个线程执行。为了保证测试结果可以追溯性,我们在业务逻辑中让线程睡眠3s。
/*** Semaphore test* @author senfel* @date 2023/4/6 11:38 * @return*/
@SpringBootTest
class ConcurrentApplicationTests {/*** 日期格式*/private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");@Testvoid testSemaphore() throws Exception {//创建线程为5的线程池ExecutorService executorService = Executors.newFixedThreadPool(5);//同时运行3个线程运行Semaphore semaphore = new Semaphore(3);//等待执行完成CountDownLatch countDownLatch = new CountDownLatch(10);for(int i = 0;i<10;i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try{//获取许可semaphore.acquire();//业务逻辑executeFun();//释放许可semaphore.release();countDownLatch.countDown();}catch (Exception e){e.printStackTrace();}}});}countDownLatch.await();System.err.println("执行完成");executorService.shutdown();}/*** 执行业务方法* @author senfel* @date 2023/4/6 11:13* @return void*/private void executeFun() {try {String startTime = formatter.format(LocalDateTime.now());Thread.sleep(3000);System.err.println("当前执行线程:"+Thread.currentThread().getName()+",开始时间:"+startTime+",结束时间"+formatter.format(LocalDateTime.now()));} catch (Exception e) {e.printStackTrace();}}}
2、执行测试方法
执行testSemaphore() 方法,我们可以在控制台看到如下结果:
当前执行线程:pool-1-thread-1,开始时间:11:37:00,结束时间11:37:03
当前执行线程:pool-1-thread-3,开始时间:11:37:00,结束时间11:37:03
当前执行线程:pool-1-thread-2,开始时间:11:37:00,结束时间11:37:03
当前执行线程:pool-1-thread-2,开始时间:11:37:03,结束时间11:37:06
当前执行线程:pool-1-thread-1,开始时间:11:37:03,结束时间11:37:06
当前执行线程:pool-1-thread-3,开始时间:11:37:03,结束时间11:37:06
当前执行线程:pool-1-thread-1,开始时间:11:37:06,结束时间11:37:09
当前执行线程:pool-1-thread-4,开始时间:11:37:06,结束时间11:37:09
当前执行线程:pool-1-thread-2,开始时间:11:37:06,结束时间11:37:09
当前执行线程:pool-1-thread-5,开始时间:11:37:09,结束时间11:37:12
执行完成
根据执行结果我们可以发现每3s有3个线程执行并输出,得出结论Semaphore可以保证每次只能同时3个线程执行!!!!
总结
多线程控制并发数目工具类Semaphore内部继承AQS抽象阻塞队列,并继承了其同步获取、释放资源的方法。Semaphore提供了acquire()获取许可、release()释放许可方法,底层当然调用的是AQS内部方法来满足同步以保证限制并发线程运行数目。
相关文章:

多线程控制并发数目工具类Semaphore
文章目录前言Semaphore原理Semaphore源码解析内部继承AQS保证同步acquire获取许可release释放许可实战演示总结前言 在多线程编码过程中,我们会用到多线程来提升运行效率。比如我们的Executors创建线程池,程序尽可能的压榨CPU资源来提升我们程序吞吐量。…...
Redis篇之五大数据类型
1、五大数据类型 4.1、String(字符串) String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象…...

Linux->文件系统磁盘文件管理
目录 1 磁盘结构 2 逻辑抽象管理磁盘 2.1 逻辑抽象 2.2 管理磁盘 2.3 补充知识 3 软硬连接 1 磁盘结构 本篇的学习需要建立在大家在脑海中有一副磁盘的结构才能进行下去,所以我会以图解的方式为大家简单讲解一下,注:博主对这一部分并不是…...

echarts tooltip文字太长换行
tooltip文字太长换行,设置了宽度也没有换行,加上一句: extraCssText: ‘max-width:300px; white-space:pre-wrap’, 没加之前是这样: 加上之后 extraCssText: ‘max-width:300px; white-space:pre-wrap’, tooltip: {trigger: &…...

Docker 部署Jira8.1.0
Jira与Confluence一样,都需要用到独立的数据库,对于数据库的安装我们不做介绍,主要介绍如何用Docker部署Jira以及对Jira进行破解的操作。 1、数据库准备 关于数据库官方文档说明:https://confluence.atlassian.com/adminjiraserv…...
枚举、模拟法(蓝桥杯卡片、数的分解为例)
枚举和模拟算法是计算机领域常用的两种基本算法。枚举算法是一种通过列举所有可能的情况来解决问题的方法。模拟算法则是通过模拟真实场景来解决问题。 枚举、模拟法 枚举算法是指将问题分解为一系列离散的情况,通过枚举所有可能的情况,逐一检查每种情…...

DC-DC升压变换器直流隔离高压输出稳压电源模块5v12v24v48v转50v110v150v220v250v300v350v500v
HRB 系列隔离宽电压输入高电压稳压输出 特点 效率高达 80%以上1*1英寸标准封装单电压输出稳压输出工作温度: -40℃~85℃阻燃封装,满足UL94-V0 要求温度特性好可直接焊在PCB 上应用 HRB 0.2~10W 系列模块电源是一种DC-DC升压变换器。该模块电源的输入电压分为&#…...
jQuery创建、添加、删除元素
一、创建元素 语法: $("<li></li>"); 动态的创建了一个 <li> 二、添加元素 1. 内部添加 1、element.append(内容) 把内容放入匹配元素内部最后面,类似原生 appendChild。 2、element.prepend(内容) 把内容放入匹…...

产品快讯丨神策数据 A/B 测试试验指标管理重磅升级
神策数据:为了更好地帮助企业管理试验指标,神策数据 A/B 测试完善了指标类型、配置方式、计算原理等,帮助分析师、运营同学等明确计算逻辑,并最大程度减少歧义以及与技术同学沟通的成本,以实现企业内部信息的有效统一。…...

游戏开发之Unity2021URP项目场景的构建
地面的修改和编辑:地面插件的使用 打开包管理器,在左边的包那里选择“Unity注册表”,在右边进行搜索“Polybrush”,之后选择右下角的安装 安装完之后要选择样本中的URP进行导入,因为我们的项目是URP渲染管线的&#x…...

数学分析:多元微积分1
卓里奇的数学分析的好处在于直接从多元函数来入手多元微积分,引出矩阵,十分自然。 紧集的概念,感觉直接用闭集去理解就行,(对于图形学来说)。 多元函数的极限,其实和单元函数并没有什么区别。 这…...
STC32G 三轮车负压电磁
文章目录前言整车效果控制思路循迹环岛处理障碍处理关键代码部分差比和以及当前速度计算角速度环速度环环岛处理障碍处理前言 年后就没怎么碰车了,到3月中旬换三轮了,可算有一点成效了,做个记录。 整车效果 三轮负压电磁慢速元素识别控制思…...
【编程小记】位运算 x -x 表示含义
位运算 x & -x 表示含义一、原码反码补码二、位运算 x & -x 表示含义三、最终结论一、原码反码补码 在计算机中,整数的数据的存储是按照补码的方式进行存储的 按照数据与0的大小,数据又被分为正数与负数 正数的原码反码补码相同。负数的原码&…...
信创PC利旧管理新模式,麒麟信安助力国家某部委实现高效云办公
2022年,国家某部委所有桌面终端均已完成信创PC替换,并将日常办公所需的办公Office套件、OA无纸化办公系统、即时通讯系统等全部迁移至信创PC,但在进行生产业务系统迁移时,该单位信创PC仍存在业务系统与不同芯片PC难适配、应用难兼…...
【玩转RT-Thread】RT-Thread内核宏定义详解(rtdef.h)
文章目录1.RT-Thread版本信息2.RT-Thrad基础数据类型定义3.RT-Thread基本数据类型的范围4.RT-Thread系统滴答时钟最大计数值5.RT-Thread IPC数据类型范围6.RT-Thread避免未使用变量警告7.编译器相关定义8.编译器相关定义9.RT-Thread错误码定义1.RT-Thread版本信息 /* RT-Threa…...

PDF转化器免费版有哪些?这几款办公达人们都在用
在现代办公中,文件的排版和格式是非常重要的,无论是发布通知或提交策划书、投档简历或是发表论文、宣传海报或是产品说明书等,我们经常使用PDF文件格式发送给他人。然而,很多人需要对PDF进行编辑修改,通常会先将其转换…...

2022MathorCup赛题B
以下所有文字均基于作者的实际经验,并不具有完全的合理性,请谨慎参考 目录 一、问题分析 (一)问题一 (二)问题二 二、预处理 (一)训练集预处理 (二)测…...

适合销售使用的CRM系统特点
销售人员抱怨CRM系统太复杂,这是一个很重要的问题。毕竟,如果系统太难使用,会导致CRM实用率和效率下降,最终影响公司的运作。在这篇文章中,我们来探讨当销售抱怨crm客户系统太复杂了,企业该如何解决。 缺少…...
项目中获取resource下文件路径的方法
String filepathrequest.getServletContext().getRealPath("/")"files\\"; 获取的当前文件在实际运行的tomcat地址目录 String path ClassUtils.getDefaultClassLoader().getResource("").getPath()"tmp/files/"; 获取的是当前文件…...

Air32F103CBT6|CCT6|KEIL-uVsion5|本地编译|STClink|(6)、Air32F103编译下载
目录 一、环境搭建 准备工作 安装支持包 二、新建工程 添加外设库支持 测试代码 三、下载烧录 一、环境搭建 准备工作 安装MDK5,具体方法请百度,安装后需要激活才能编译大文件 下载安装AIR32F103的SDK:luatos-soc-air32f103: Air32f…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...