JUC并发编程之Semaphore-应用与深度源码剖析
目录
JUC并发编程之Semaphore-应用与深度源码剖析
1. Semaphore 是什么?
2.怎么使用Semaphore?
2.1构造方法
2.2 重要方法
2.3 基本使用
需求场景
基础版代码实现
tryAcquire()引入代码实现
acquireUninterruptibly(),acquire()对比代码实现
3.源码剖析【重点】
底层结构图:
思路总结:
semaphore.acquire():
semaphore.release():
JUC并发编程之Semaphore-应用与深度源码剖析
1. Semaphore 是什么?
Semaphore字面意思是信号量的意思,它的作用就是控制访问特定资源的线程数目,底层依赖AQS的状态State,是在生产当中比较常用的一个工具类。
2.怎么使用Semaphore?
2.1构造方法
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
permits表示许可线程的数量
fair表示公平性,如果这个设为true的话,下一次执行的线程就会是等待最久的线程
2.2 重要方法
public void acquire() throws InterruptedException
public void release()
tryAcquire(int args,long timeout, TimeUnit unit)
- acquire() 表示阻塞并获取许可
- release() 表示释放许可
2.3 基本使用
需求场景
资源访问,服务限流Hystrix里面限流底层就是基于信号量的方式,如图所示:

基础版代码实现
/*** @Description: TODO* @Author: etcEriksen* @Date: 2023/3/7**/
@Slf4j
@SuppressWarnings({"all"})
public class SemaphoreRunner {public static void main(String[] args) {//构造参数为:2,表示的含义为:该Semaphore所带有的总公共资源为2Semaphore semaphore = new Semaphore(2);for (int i = 0; i < 10; i++) {new Thread(new Task(semaphore,"leomessi:"+i)).start();}}static class Task extends Thread {Semaphore semaphore ;public Task(Semaphore semaphore,String tname) {super(tname) ;this.semaphore = semaphore ;}@Overridepublic void run() {try {//semaphore.acquireUninterruptibly();semaphore.acquire(2);//获取2个公共资源才可以通过一个线程 【带有中断抛出异常的机制】log.info(Thread.currentThread().getName()+":aquire at time:" + System.currentTimeMillis()) ;Thread.sleep(5000) ;semaphore.release(2) ;//归还公共资源,并且归还的公共资源数量要和一个线程通过时获取的公共资源数量要持平} catch (Exception e) {e.printStackTrace();}}public void fallback() {log.info("降级");}}}
代码分析:

运行结果:

tryAcquire()引入代码实现
/*** @Description: TODO* @Author: etcEriksen* @Date: 2023/3/7**/
@Slf4j
@SuppressWarnings({"all"})
public class SemaphoreRunner {public static void main(String[] args) {//构造参数为:2,表示的含义为:该Semaphore所带有的总公共资源为2Semaphore semaphore = new Semaphore(2);for (int i = 0; i < 10; i++) {new Thread(new Task(semaphore,"leomessi:"+i)).start();}}static class Task extends Thread {Semaphore semaphore ;public Task(Semaphore semaphore,String tname) {super(tname) ;this.semaphore = semaphore ;}@Overridepublic void run() {try {
// //semaphore.acquireUninterruptibly();// semaphore.acquire();//获取2个公共资源才可以通过一个线程 【带有中断抛出异常的机制】
// Thread.sleep(5000) ;
// semaphore.release(2) ;//归还公共资源,并且归还的公共资源数量要和一个线程通过时获取的公共资源数量要持平if (semaphore.tryAcquire(500, TimeUnit.MILLISECONDS)) {log.info(Thread.currentThread().getName()+":aquire at time:" + System.currentTimeMillis()) ;Thread.sleep(5000);semaphore.release();//释放公共资源} else {//如果500毫秒线程还没有获取到相对应的2个公共资源,那么降级处理fallback();}} catch (Exception e) {e.printStackTrace();}}public void fallback() {log.info("降级");}}}
分析代码:这里结合了降级处理

运行结果:

acquireUninterruptibly(),acquire()对比代码实现
acquire():当线程被中断后,会抛出InterruptException异常。
acquireUninterruptibly():当线程被中断后,不会抛出异常。
acquireUninterruptibly(): 结合代码分析

运行结果

acquire():结合代码分析

运行结果:

3.源码剖析【重点】
底层结构图:
ProcessOn Flowchart

思路总结:
初始化Semaphore对象时指定总资源数量,多个线程进来时会去竞争该公共资源,但是在公平锁的情况下,会维护一个CLH阻塞队列,该队列为公平队列,从前往后进行唤醒获取公共资源。当公共资源不够当前线程使用时或CLH阻塞队列存在节点时,新进来的线程对象都会被封装为Node节点加入到CLH阻塞队列的尾部,公平等待时机。非公平锁时,与之正好相反。
semaphore.acquire():
1.

2.

3.

acquireSharedInterruptibly()调用的tryAcquireShared解析:

acquireSharedInterruptibly()调用的doAcquireSharedInterruptibly解析:

很多相同的源码在之前的源码分析中都详细介绍了,所以这里只记录新出现的源码思路:

注:doAcquireSharedInterruptibly方法调用的tryAcquireShared方法

4.应用层调用Interrupt()方法

应用层的interrupt()中断方法调用后,底层park阻塞被中断,那么继续向下执行代码:
interrupt()方法给当前线程打上中断标识啦,所以调用interrupted()方法时返回true并且消除中断标识。


semaphore.release():
该方法后之前源码分析的lock.unlock()的思路基本一致。简略记录:
1.

2.

3.


相关文章:
JUC并发编程之Semaphore-应用与深度源码剖析
目录 JUC并发编程之Semaphore-应用与深度源码剖析 1. Semaphore 是什么? 2.怎么使用Semaphore? 2.1构造方法 2.2 重要方法 2.3 基本使用 需求场景 基础版代码实现 tryAcquire()引入代码实现 acquireUninterruptibly(),acquire()对比代码实现 3.…...
JWT详细介绍使用
一、JWT介绍 JWT是JSON Web Token的缩写,即JSON Web令牌,是一种自包含令牌。 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务…...
C/C++开发,无可避免的多线程(篇六).线程池封装类
一、线程池概念 线程池是一种多线程处理方式,它包含一个线程工作队列和一个任务队列。当有任务需要处理时,线程池会从线程工作队列中取出一个空闲线程来处理任务,如果线程工作队列中没有空闲线程,则任务会被放入任务队列中等待处理…...
HIVE中如何实现针对IPv6 CIDR的查询
Hive默认情况下不支持IPv6 CIDR查询,因为IPv6 CIDR查询需要使用一些额外的函数。 但是可以通过使用UDF(用户自定义函数)来实现这一点。 IPv6 CIDR表示为网络地址/前缀长度,其中网络地址是一个IPv6地址,前缀长度是一个介于0和128之间的整数,表示网络地址中前多少位是网络…...
【微信小程序】-- 生命周期(二十八)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...
Kafka 概述
Kafka 概述Broker消费者Kafka 属于分布式的消息引擎系统,主要功能 :提供一套完备的消息发布与订阅解决方案 生产者和消费者都是客户端(Clients): 生产者(Producer):向主题发布消息…...
详解Java8中如何通过方法引用获取属性名/::的使用
在我们开发过程中常常有一个需求,就是要知道实体类中Getter方法对应的属性名称(Field Name),例如实体类属性到数据库字段的映射,我们常常是硬编码指定 属性名,这种硬编码有两个缺点。 1、编码效率低&#x…...
0106广度优先搜索和最短路径-无向图-数据结构和算法(Java)
1 单点最短路径 单点最短路径。 给定一幅图和一个起点s,回答“从s到给定目的顶点v是否存在一条路径?如果有,找出其中最短的那条(所含边数最少)。“等类似问题。 深度优先搜索在这个问题上没有什么作为,因为…...
僵尸(Zombie)进程
文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程 进程完成工作后(执行完 main 函数中的程序后)应被销毁,但有时这些进程将变成僵尸进程,占用系统中的重要资源。这…...
JS实现:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
题目:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 数列是 1,1,2,3,5,8,13,21....观察可以看出来从第三个数字开始…...
Verilog如何编写一个基础的Testbench
本文将讲述如何使用Verilog 编写一个基础的测试脚本(testbench)。在考虑一些关键概念之前,先来看看testbench的架构是什么样的。架构包括建模时间、initial块(initial block)和任务(task)。此文…...
基于JavaEE社区物业管理系统开发与实现(附源码资料)
文章目录1. 适用人群2. 你将收获3.项目简介4.技术栈5.测试账号6.部分功能模块展示6.1.管理员6.2.业主1. 适用人群 本课程主要是针对计算机专业相关正在做毕业设计或者是需要实战项目的Java开发学习者。 2. 你将收获 提供:项目源码、项目文档、数据库脚本、软件工…...
问一下ChatGPT:DIKW金字塔模型
经常看到这张DIKW金字塔模型图,还看到感觉有点过份解读的图,后面又加上了insight,impact等内容。 Data:是数据,零散的、无规则的呈现到人们眼前,如果你只看到这些数字,如果没有强大的知识背景&a…...
javaScript基础面试题 ---闭包
闭包1、闭包是什么?2、闭包可以解决什么问题?3、闭包的缺点1、闭包是什么? 闭包是一个函数加上到创建这个函数的作用域的链接,就是一个作用域可以访问到另一个作用域的变量,闭包‘关闭’了函数的自由变量 function f…...
如何自定义您的网站实时聊天图标
实时聊天图标是您网站上的一个按钮,可在访问者单击时打开实时聊天。它代表了您的企业与客户沟通的门户。这是您的网站访问者与您联系、提出问题和接收个性化推荐的一种方式,聊天图标的设计最好是简单且引人入胜,个性化的图标往往更能提现企业…...
Vue侦听器Watch
31. Vue侦听器Watch 1. 定义 Watch是Vue.js提供的一个观察者模式,用于监听数据的变化并执行相应的回调函数。虽然计算属性Computed在大多数情况下更合适,但有时也需要一个自定义的侦听器Watch。因为在有些情况下,我们需要在状态变化时执行一…...
云快充研发中心平台架构师谈云原生稳定性建设之路
作者:吕周洋 大家好,我是来自云快充研发中心的平台架构师吕周洋,今天我给大家分享云快充云原生稳定性之路。 点击查看:云快充研发中心平台架构师 吕周洋:云快充云原生稳定性治理之路 云快充成立于2016年,…...
ENVI IDL学习笔记之基本操作
前言ENVI IDL(交互式数据语言)是一个通用的科学计算包,它提供了一套数学函数、数据分析工具,以及一些科学可视化和动画工具。IDL 是 ENVI 图像处理和分析软件的基础,可用于编写脚本并自动执行许多使用 ENVI 图形用户界…...
多线程面试题
1. Sychronized的锁升级过程是怎样的? 2. Tomcat 中为什么要使用自定义类加载器? 3. 说说对线程安全的理解 4. 对守护线程的理解 5. 并发、并行、串行之间的区别 6. Java死锁如何避免? 7. 谈谈你对AQS的理解,AQS如何实现可重入锁&…...
YARN运行流程
YARN是Hadoop资源管理器,他是一个通用资源管理平台和调度平台,可为上层应用提供统一的资源管理和调度,MapReduce等运算程序则相当于运行于操作系统上的应用程序,YARN为这些程序提供运算所需的资源内存、cpu。 YARN并不清楚用户提…...
Taotoken用量看板如何帮助团队清晰掌控API成本
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Taotoken用量看板如何帮助团队清晰掌控API成本 对于使用大模型API进行开发的团队而言,成本控制一直是一个核心的管理挑…...
3步实现企业级智能地址解析:Java开发者的终极效率提升指南
3步实现企业级智能地址解析:Java开发者的终极效率提升指南 【免费下载链接】address-parse Java 版智能解析收货地址 项目地址: https://gitcode.com/gh_mirrors/addr/address-parse 在电商、物流、外卖等数字化业务高速发展的今天,地址解析已成为…...
AI原生测试生成自动化落地全景图(2026奇点大会闭门报告首次解禁)
更多请点击: https://intelliparadigm.com 第一章:AI原生测试生成自动化:2026奇点智能技术大会测试用例生成 核心范式跃迁 传统基于规则或模板的测试生成已无法应对大模型驱动下的动态接口、多模态输入与上下文敏感逻辑。2026奇点智能技术大…...
Ubuntu 服务器运维如何利用 Taotoken 实现大模型 API 的容灾与成本控制
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 Ubuntu 服务器运维如何利用 Taotoken 实现大模型 API 的容灾与成本控制 对于在 Ubuntu 生产服务器上集成 AI 功能的运维工程师而言…...
为AI Agent构建文件交付通道:OpenClaw File Links Tool部署与集成指南
1. 项目概述:为AI Agent构建专属文件交付通道如果你正在开发或使用AI Agent,比如AutoGPT、Claude Desktop的MCP工具,或者任何需要执行文件操作(如数据分析、文档生成、网页抓取)的自动化程序,那么一个核心痛…...
3分钟让模糊录音变清晰:VoiceFixer语音修复神器使用指南
3分钟让模糊录音变清晰:VoiceFixer语音修复神器使用指南 【免费下载链接】voicefixer General Speech Restoration 项目地址: https://gitcode.com/gh_mirrors/vo/voicefixer 你是否曾经因为录音质量太差而烦恼?那些充满杂音的会议录音、模糊不清…...
STM32新手避坑指南:正点原子、野火、慧净、小马飞控的Systick延时函数到底差在哪?
STM32开发板Systick延时函数深度对比:从原理到避坑实战 第一次接触STM32开发时,我对着四块不同品牌的开发板愣了半天——正点原子、野火、慧净、小马飞控,每家的例程里Systick延时函数实现都不一样。有的用72MHz时钟,有的用9MHz&a…...
Blender到虚幻引擎5分钟极速迁移:Datasmith导出插件实战指南
Blender到虚幻引擎5分钟极速迁移:Datasmith导出插件实战指南 【免费下载链接】bl_datasmith UE Datasmith importer/exporter for Blender 项目地址: https://gitcode.com/gh_mirrors/bl/bl_datasmith 你是否曾在Blender中创建了完美的3D场景,却在…...
5G网络“自动驾驶”实战:手把手理解O-RAN RIC中的xApp与冲突缓解机制
5G网络“自动驾驶”实战:O-RAN RIC中的xApp冲突仲裁与协同优化 当五个交通信号灯同时指挥同一个路口时会发生什么?这正是5G O-RAN网络中多个xApp争夺无线资源控制权时面临的现实挑战。在东京某商业区实测中,三个未经协调的xApp同时调整基站参…...
Linux du 命令深度解析:从磁盘占用统计到目录空间分析
du 的核心原理:递归遍历 block 计数 du 的本质是统计文件占用的磁盘块数量,而不是文件大小。这两者有微妙但重要的区别。 底层实现通过 stat() 系统调用获取每个文件的 st_blocks 字段: // 简化版 du 实现核心逻辑 #include <sys/stat.h> #include <dirent.h>o…...
