当前位置: 首页 > news >正文

【线程同步工具】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。 一个信号量就是一个计数器&#xff0c; 可用于保护对一个或多个共享资源的访问。 当一个线程要访问多个共享资源中的一个时&#xff0c;它首先需要获得一个信号量。如果信号量内部的计数器的…...

获取实时天气

一、用天气API&#xff08;需要付费&#xff09; 网址&#xff1a;https://www.tianqiapi.com/请求方式及url&#xff1a;请求方式&#xff1a;GET接口地址&#xff1a;https://tianqiapi.com/free/day请求示例https://www.tianqiapi.com/free/day?appid_____&appsecret__…...

【数据库】redis数据持久化

目录 数据持久化 一&#xff0c; RDB 1&#xff0c; 什么是RDB 2&#xff0c;持久化流程 3&#xff0c; 相关配置 案例演示&#xff1a; 4&#xff0c; 备份和恢复 1、备份 2、恢复 3&#xff0c;优势 4&#xff0c; 劣势 二&#xff0c;AOF 1&#xff0c;什么是A…...

前端编译、JIT编译、AOT编译

一、前端编译&#xff1a;java设计之初就是强调跨平台&#xff0c;通过javac将源文件编译成于平台无关的class文件&#xff0c; 它定义了执行 Java 程序所需的所有信息&#xff08;许多Java"语法糖"&#xff0c;是在这个阶段完成的&#xff0c;不依赖虚拟机&#xff…...

父子组件中,子组件调用父组件的方法

父子组件中&#xff0c;子组件调用父组件的方法 方法一&#xff1a;直接在子组件中通过this.$parent.event来调用父组件的方法 父组件 <template><p><child>父组件</child></p> </template> <script>import child from ~/compone…...

第七章.深度学习

第七章.深度学习 7.1 深度学习 深度学习是加深了层的深度神经网络。 1.加深层的好处 1).可以减少网络的参数数量 5*5的卷积运算示例&#xff1a; 重复两次3*3的卷积层示例&#xff1a; 图像说明&#xff1a; ①.一次5 * 5的卷积运算的区域可以由两次3 * 3的卷积运算抵消&a…...

小学生学Arduino---------点阵(三)动态的显示与清除

学习目标&#xff1a; 1、理解“整数值”的概念与使用 2、理解“N1”指令的意义 3、掌握“反复执行多次”指令的使用 4、掌握屏幕模块的清除功能指令 5、理解“反复执行”指令与“反复执行多次”指令的嵌套使用 6、搭建电路图 7、编写程序 效果&#xff1a; 整数包括&#xf…...

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 学习笔记(二)—— 复合类型

数组 当我们只是定义了数组&#xff0c;而没有对数组进行初始化时&#xff0c;那数组的值将是未定义的。 在对数组进行初始化时&#xff0c;如果只对数组的一部分进行初始化&#xff0c;编译器会将把其他元素自动设置为0。 #include <iostream>using namespace std;in…...

代码随想录算法训练营第七天 | 454.四数相加II 、 383. 赎金信、15. 三数之和、18. 四数之和 、总结

打卡第七天&#xff0c;还是哈希表。 今日任务 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语言中帮助用户实现高效的向量化运算的一系列函数&#xff0c;包括apply,lapply,sapply,vapply等。 apply() apply函数以列或行为单位进行循环操作&#xff0c;可以处理matrix、array数据&#xff0c;返回一个向量或matrix。 apply(data,1/2,fuc…...

读书笔记可读性素材

《深入理解Java虚拟机》 《深入理解Java虚拟机》 《深入理解Java虚拟机》 本地方法栈&#xff08;Native Method Stacks&#xff09; 本地方法栈&#xff08;Native Method Stacks&#xff09; 本地方法栈&#xff08;Native Method Stacks&#xff09; -----------------…...

【C++】vector 模拟实现

vectorvector 容器vector 基本使用vector 定义库中各类接口的使用迭代器容量相关接口元素访问相关接口元素修改相关接口模拟实现 vector前期准备构造与析构赋值运算符重载迭代器相关容量相关元素访问相关元素的修改相关二维数组的创建对于自定义类型数据的测试vector 容器 C S…...

canvas初体验

canvas介绍 Canvas 最初由Apple于2004 年引入&#xff0c;用于Mac OS X WebKit组件&#xff0c;为仪表板小部件和Safari浏览器等应用程序提供支持。后来&#xff0c;它被Gecko内核的浏览器&#xff08;尤其是Mozilla Firefox&#xff09;&#xff0c;Opera和Chrome实现&#x…...

JavaWeb12-线程通讯(线程等待和唤醒)

目录 1.方法介绍 1.1.wait()/wait(long timeout)&#xff1a;让当前线程进入等待状态。 1.1.1.wait执行流程 1.1.2.wait结束等待的条件 1.1.3.wait() VS wait(long timeout) 1.1.4.为什么wait要放在Object中&#xff1f; --->PS&#xff1a;wait(0) 和 sleep(0) 的区…...

江苏专转本如何事半功倍的备考

专转本如何事半功倍的备考 一个人学习成绩的优劣取决于他的学习能力&#xff0c;学习能力包括三个要素&#xff1a;规范的学习行为&#xff1b;良好的学习习惯&#xff1b;有效的学习方法。有了规范的学习行为才能培养出良好的学习习惯&#xff0c;形成了良好的学习习惯就会形成…...

linux下安装mongoDB

一、下载mongoDB包 下载地址&#xff1a; https://www.mongodb.com/try/download/community 个人建议&#xff1a;如果是学习阶段&#xff0c;使用5以下版本更好些。 二、安装及配置 1、安装 # 1、解压 $ tar -zxvf mongodb-linux-x86_64-rhel70-4.4.19-rc1.tgz# 2、迁移目…...

掌握MySQL分库分表(七)广播表、绑定表实战,水平分库+分表实现及之后的查询和删除操作

文章目录什么是广播表广播表实战数据库配置表Java配置实体类配置文件测试广播表水平分库分表配置文件运行测试什么是绑定表&#xff1f;绑定表实战配置数据库配置Java实体类配置文件运行测试水平分库分表后的查询和删除操作查询操作什么是广播表 指所有的分片数据源中都存在的…...

企业为什么需要数据可视化报表

数据可视化报表是在商业环境、市场环境已经改变之后&#xff0c;发展出来为当前企业提供替代解决办法的重要方案。而且信息化、数字化时代&#xff0c;很多企业已经进行了初步的信息化建设&#xff0c;沉淀了大量业务数据&#xff0c;这些数据作为企业的资产&#xff0c;是需要…...

5个有效的华为(HUAWEI)手机数据恢复方法

5个有效的手机数据恢复方法 华为智能手机中的数据丢失比许多人认为的更为普遍。发生这种类型的丢失有多种不同的原因&#xff0c;因此数据恢复软件的重要性。您永远不知道您的智能手机何时会在这方面垮台&#xff1b;因此&#xff0c;预防总比哀叹好&#xff0c;这就是为什么众…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

《Docker》架构

文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器&#xff0c;docker&#xff0c;镜像&#xff0c;k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…...

C# winform教程(二)----checkbox

一、作用 提供一个用户选择或者不选的状态&#xff0c;这是一个可以多选的控件。 二、属性 其实功能大差不差&#xff0c;除了特殊的几个外&#xff0c;与button基本相同&#xff0c;所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数&#xff0c;单位是赫兹&#xff08;Hz&#xff09;。 60Hz 屏幕&#xff1a;每秒刷新 60 次&#xff0c;每次刷新间隔约 16.67ms 90Hz 屏幕&#xff1a;每秒刷新 90 次&#xff0c;…...

StarRocks 全面向量化执行引擎深度解析

StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计&#xff0c;相比传统行式处理引擎&#xff08;如MySQL&#xff09;&#xff0c;性能可提升 5-10倍。以下是分层拆解&#xff1a; 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...

【技巧】dify前端源代码修改第一弹-增加tab页

回到目录 【技巧】dify前端源代码修改第一弹-增加tab页 尝试修改dify的前端源代码&#xff0c;在知识库增加一个tab页"HELLO WORLD"&#xff0c;完成后的效果如下 [gif01] 1. 前端代码进入调试模式 参考 【部署】win10的wsl环境下启动dify的web前端服务 启动调试…...