【线程同步工具】Semaphore源码解析
控制对资源的一个或多个副本的并发访问
Java API 提供了一种信号量机制 Semaphore。 一个信号量就是一个计数器, 可用于保护对一个或多个共享资源的访问。
当一个线程要访问多个共享资源中的一个时,它首先需要获得一个信号量。如果信号量内部的计数器的值大于 0,那么信号量就递减计数器并允许线程访问。计数器的值大于 0 意味着存在可用的空闲资源,所以线程能够访问并使用这些资源中的一个。
如果计数器的值为 0,信号量会让线程休眠,直到计数器的值大于 0。计数器的值为 0 意味着所有共享资源都被其他线程占用了,所以当前想要使用资源的线程,必须等待其中一个资源被释放 。
Semaphore源码分析
Sync类
/*** Synchronization implementation for semaphore. Uses AQS state* to represent permits. Subclassed into fair and nonfair* versions.*/
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;}}protected final boolean tryReleaseShared(int releases) {for (;;) {int current = getState();int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");if (compareAndSetState(current, next))return true;}}final void reducePermits(int reductions) {for (;;) {int current = getState();int next = current - reductions;if (next > current) // underflowthrow new Error("Permit count underflow");if (compareAndSetState(current, next))return;}}final int drainPermits() {for (;;) {int current = getState();if (current == 0 || compareAndSetState(current, 0))return current;}}
}
这是 Semaphore 中定义的一个抽象内部类 Sync,用于实现信号量的同步机制。该类继承了 AbstractQueuedSynchronizer,并重写了其中的一些方法,同时提供了一些新的方法来实现 Semaphore 的不同操作。
其中,Sync 类有一个整型变量 state,用于表示当前 Semaphore 中的可用许可数量。每当一个线程获取了一个许可时,state 的值减 1,每当一个线程释放了一个许可时,state 的值加 1。
Sync 类中定义了以下方法:
- 构造方法 Sync(int permits):构造一个具有指定许可数的 Sync 实例,将许可数存储在 state 中。
- getPermits():获取当前 Semaphore 中可用的许可数量。
- nonfairTryAcquireShared(int acquires):非公平模式下尝试获取指定数量的许可。如果当前可用的许可数量不足,则返回负数;否则原子减少 state 的值并返回剩余许可数。
- tryReleaseShared(int releases):尝试释放指定数量的许可,如果释放成功则返回 true,否则返回 false。
- reducePermits(int reductions):减少 Semaphore 中可用的许可数量,该操作不可撤回。如果减少的数量大于当前可用的许可数量,将抛出异常。
- drainPermits():返回当前 Semaphore 中可用的所有许可,并将 state 值原子更新为 0。如果当前没有可用的许可,返回值为 0。
其中,许可的获取和释放操作是通过对 state 进行原子操作来实现的。
在代码实现中,nonfairTryAcquireShared() 方法使用了自旋操作,不断检查 state 的值,直到当前线程成功获取许可或者其他线程释放许可后,才退出自旋。
reducePermits() 方法是用于减少 Semaphore 许可数量的,它使用自旋方式将当前 state 值减去指定数量的许可,并且对减去后的结果进行检查,以确保结果值不会小于 0。
在减少许可的时候,也使用了原子操作,以保证多个线程同时调用 reducePermits() 方法时不会导致并发问题。
FairSync类
/*** Fair version*/
static final class FairSync extends Sync {private static final long serialVersionUID = 2014338818796000944L;FairSync(int permits) {super(permits);}protected int tryAcquireShared(int acquires) {for (;;) {if (hasQueuedPredecessors())return -1;int available = getState();int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}
}
这段代码用于公平模式下获取许可证。先检测前面是否有排队的,如果有排队的则获取许可失败,进入队列排队,否则尝试原子更新state的值。
hasQueuedPredecessors()判断当前线程是否在等待队列中。
public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}
这段代码的实现思路比较复杂,我们先来了解一点AQS队列的结构和细节。在AQS队列中,每个等待线程都被包装成一个Node对象,这些Node对象通过next指针形成了一个FIFO(先进先出)队列。Node节点中包含了线程本身以及一些状态信息,如前继节点、后继节点等。等待队列的头结点(head)是当前持有许可证的线程所对应的节点,等待队列的尾节点(tail)是最后一个正在等待的线程所对应的节点。
再来接着分析hasQueuedPredecessors()的实现逻辑,首先它会先获取等待队列中的头节点和尾节点,然后判断头节点和尾节点是否相同。如果相同,说明当前线程是第一个在等待队列中的线程,返回false。否则,获取头节点的下一个节点,判断下一个节点的线程是否为当前线程,如果是,则说明当前线程在等待队列中,返回true,否则返回false。
Semaphore用法
使用 Semaphore 实现限流
限流是一种在分布式系统中常用的流量控制方式,可以防止因流量过大而导致的系统崩溃、服务降级等问题。一般情况下,限流是通过限制并发请求、请求速率等方式来实现的,比如使用令牌桶、漏桶等算法。在网关层进行限流可以减轻后端服务的压力,保证服务的可用性和稳定性。而在某些场景下,也可以在应用程序中自己实现限流,比如秒杀场景中限制并发请求的数量,避免过多的请求导致系统崩溃。
下面我们使用 Semaphore 实现一个简单的限流功能:
public class SemaphoreTest {public static final Semaphore SEMAPHORE = new Semaphore(100);public static final AtomicInteger failCount = new AtomicInteger(0);public static final AtomicInteger successCount = new AtomicInteger(0);public static void main(String[] args) {for (int i = 0; i < 1000; i++) {new Thread(()->seckill()).start();}}public static boolean seckill() {if (!SEMAPHORE.tryAcquire()) {System.out.println("no permits, count="+failCount.incrementAndGet());return false;}try {// 处理业务逻辑Thread.sleep(2000);System.out.println("seckill success, count="+successCount.incrementAndGet());} catch (InterruptedException e) {// todo 处理异常e.printStackTrace();} finally {SEMAPHORE.release();}return true;}
}
作者简介
鑫茂,深圳,Java开发工程师,2022年3月参加工作。
喜读思维方法、哲学心理学以及历史等方面的书,偶尔写些文字。
希望通过文章,结识更多同道中人。
相关文章:
【线程同步工具】Semaphore源码解析
控制对资源的一个或多个副本的并发访问 Java API 提供了一种信号量机制 Semaphore。 一个信号量就是一个计数器, 可用于保护对一个或多个共享资源的访问。 当一个线程要访问多个共享资源中的一个时,它首先需要获得一个信号量。如果信号量内部的计数器的…...
获取实时天气
一、用天气API(需要付费) 网址:https://www.tianqiapi.com/请求方式及url:请求方式:GET接口地址:https://tianqiapi.com/free/day请求示例https://www.tianqiapi.com/free/day?appid_____&appsecret__…...
【数据库】redis数据持久化
目录 数据持久化 一, RDB 1, 什么是RDB 2,持久化流程 3, 相关配置 案例演示: 4, 备份和恢复 1、备份 2、恢复 3,优势 4, 劣势 二,AOF 1,什么是A…...
前端编译、JIT编译、AOT编译
一、前端编译:java设计之初就是强调跨平台,通过javac将源文件编译成于平台无关的class文件, 它定义了执行 Java 程序所需的所有信息(许多Java"语法糖",是在这个阶段完成的,不依赖虚拟机ÿ…...
父子组件中,子组件调用父组件的方法
父子组件中,子组件调用父组件的方法 方法一:直接在子组件中通过this.$parent.event来调用父组件的方法 父组件 <template><p><child>父组件</child></p> </template> <script>import child from ~/compone…...
第七章.深度学习
第七章.深度学习 7.1 深度学习 深度学习是加深了层的深度神经网络。 1.加深层的好处 1).可以减少网络的参数数量 5*5的卷积运算示例: 重复两次3*3的卷积层示例: 图像说明: ①.一次5 * 5的卷积运算的区域可以由两次3 * 3的卷积运算抵消&a…...
小学生学Arduino---------点阵(三)动态的显示与清除
学习目标: 1、理解“整数值”的概念与使用 2、理解“N1”指令的意义 3、掌握“反复执行多次”指令的使用 4、掌握屏幕模块的清除功能指令 5、理解“反复执行”指令与“反复执行多次”指令的嵌套使用 6、搭建电路图 7、编写程序 效果: 整数包括…...
opencv图片处理
目录1 图片处理1.1 显示图片1.2 旋转图片1.3 合并图片1.4、Mat类1.4.1、像素的储存结构1.4.2、访问像素数据1.6、rgb转灰度图1.7、二值化1.8、对比度和亮度1.9、图片缩放1.9.1、resize临近点算法双线性内插值1.9.2、金字塔缩放1.10、图片叠加1 图片处理 1.1 显示图片 #includ…...
C++ Primer Plus 学习笔记(二)—— 复合类型
数组 当我们只是定义了数组,而没有对数组进行初始化时,那数组的值将是未定义的。 在对数组进行初始化时,如果只对数组的一部分进行初始化,编译器会将把其他元素自动设置为0。 #include <iostream>using namespace std;in…...
代码随想录算法训练营第七天 | 454.四数相加II 、 383. 赎金信、15. 三数之和、18. 四数之和 、总结
打卡第七天,还是哈希表。 今日任务 454.四数相加II383.赎金信15.三数之和18.四数之和总结 454.四数相加II 代码随想录 class Solution { public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, ve…...
apply函数族
apply函数族 apply函数族是R语言中帮助用户实现高效的向量化运算的一系列函数,包括apply,lapply,sapply,vapply等。 apply() apply函数以列或行为单位进行循环操作,可以处理matrix、array数据,返回一个向量或matrix。 apply(data,1/2,fuc…...
读书笔记可读性素材
《深入理解Java虚拟机》 《深入理解Java虚拟机》 《深入理解Java虚拟机》 本地方法栈(Native Method Stacks) 本地方法栈(Native Method Stacks) 本地方法栈(Native Method Stacks) -----------------…...
【C++】vector 模拟实现
vectorvector 容器vector 基本使用vector 定义库中各类接口的使用迭代器容量相关接口元素访问相关接口元素修改相关接口模拟实现 vector前期准备构造与析构赋值运算符重载迭代器相关容量相关元素访问相关元素的修改相关二维数组的创建对于自定义类型数据的测试vector 容器 C S…...
canvas初体验
canvas介绍 Canvas 最初由Apple于2004 年引入,用于Mac OS X WebKit组件,为仪表板小部件和Safari浏览器等应用程序提供支持。后来,它被Gecko内核的浏览器(尤其是Mozilla Firefox),Opera和Chrome实现&#x…...
JavaWeb12-线程通讯(线程等待和唤醒)
目录 1.方法介绍 1.1.wait()/wait(long timeout):让当前线程进入等待状态。 1.1.1.wait执行流程 1.1.2.wait结束等待的条件 1.1.3.wait() VS wait(long timeout) 1.1.4.为什么wait要放在Object中? --->PS:wait(0) 和 sleep(0) 的区…...
江苏专转本如何事半功倍的备考
专转本如何事半功倍的备考 一个人学习成绩的优劣取决于他的学习能力,学习能力包括三个要素:规范的学习行为;良好的学习习惯;有效的学习方法。有了规范的学习行为才能培养出良好的学习习惯,形成了良好的学习习惯就会形成…...
linux下安装mongoDB
一、下载mongoDB包 下载地址: https://www.mongodb.com/try/download/community 个人建议:如果是学习阶段,使用5以下版本更好些。 二、安装及配置 1、安装 # 1、解压 $ tar -zxvf mongodb-linux-x86_64-rhel70-4.4.19-rc1.tgz# 2、迁移目…...
掌握MySQL分库分表(七)广播表、绑定表实战,水平分库+分表实现及之后的查询和删除操作
文章目录什么是广播表广播表实战数据库配置表Java配置实体类配置文件测试广播表水平分库分表配置文件运行测试什么是绑定表?绑定表实战配置数据库配置Java实体类配置文件运行测试水平分库分表后的查询和删除操作查询操作什么是广播表 指所有的分片数据源中都存在的…...
企业为什么需要数据可视化报表
数据可视化报表是在商业环境、市场环境已经改变之后,发展出来为当前企业提供替代解决办法的重要方案。而且信息化、数字化时代,很多企业已经进行了初步的信息化建设,沉淀了大量业务数据,这些数据作为企业的资产,是需要…...
5个有效的华为(HUAWEI)手机数据恢复方法
5个有效的手机数据恢复方法 华为智能手机中的数据丢失比许多人认为的更为普遍。发生这种类型的丢失有多种不同的原因,因此数据恢复软件的重要性。您永远不知道您的智能手机何时会在这方面垮台;因此,预防总比哀叹好,这就是为什么众…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
多元隐函数 偏导公式
我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式,给定一个隐函数关系: F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 🧠 目标: 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z、 …...
Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合
无论是python,或者java 的大型项目中,都会涉及到 自身平台微服务之间的相互调用,以及和第三发平台的 接口对接,那在python 中是怎么实现的呢? 在 Python Web 开发中,FastAPI 和 Django 是两个重要但定位不…...
leetcode73-矩阵置零
leetcode 73 思路 记录 0 元素的位置:遍历整个矩阵,找出所有值为 0 的元素,并将它们的坐标记录在数组zeroPosition中置零操作:遍历记录的所有 0 元素位置,将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...
结构化文件管理实战:实现目录自动创建与归类
手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题,进而引发后续程序异常。使用工具进行标准化操作,能有效降低出错概率。 需要快速整理大量文件的技术用户而言,这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB,…...
