Semaphore 详解

1、Semaphore 是什么

Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。

2、使用场景

通常用于那些资源有明确访问数量限制的场景,常用于限流 。

比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。

3、Semaphore常用方法说明

acquire()  
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
​
acquire(int permits)  
获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。acquireUninterruptibly() 
获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。tryAcquire()
尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
​
tryAcquire(long timeout, TimeUnit unit)
尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
​
release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
​
hasQueuedThreads()
等待队列里是否还存在等待线程。
​
getQueueLength()
获取等待队列里阻塞的线程数。
​
drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。
​
availablePermits()
返回可用的令牌数量。

4、用semaphore 实现停车场提示牌功能。

每个停车场入口都有一个提示牌,上面显示着停车场的剩余车位还有多少,当剩余车位为0时,不允许车辆进入停车场,直到停车场里面有车离开停车场,这时提示牌上会显示新的剩余车位数。

业务场景 :

1、停车场容纳总停车量10。

2、当一辆车进入停车场后,显示牌的剩余车位数响应的减1.

3、每有一辆车驶出停车场后,显示牌的剩余车位数响应的加1。

4、停车场剩余车位不足时,车辆只能在外面等待。

代码:

public class TestCar {
​//停车场同时容纳的车辆10private  static  Semaphore semaphore=new Semaphore(10);
​public static void main(String[] args) {
​//模拟100辆车进入停车场for(int i=0;i<100;i++){
​Thread thread=new Thread(new Runnable() {public void run() {try {System.out.println("===="+Thread.currentThread().getName()+"来到停车场");if(semaphore.availablePermits()==0){System.out.println("车位不足,请耐心等待");}semaphore.acquire();//获取令牌尝试进入停车场System.out.println(Thread.currentThread().getName()+"成功进入停车场");Thread.sleep(new Random().nextInt(10000));//模拟车辆在停车场停留的时间System.out.println(Thread.currentThread().getName()+"驶出停车场");semaphore.release();//释放令牌,腾出停车场车位} catch (InterruptedException e) {e.printStackTrace();}}},i+"号车");
​thread.start();
​}
​}
}
​

5、Semaphore实现原理

(1)、Semaphore初始化。

Semaphore semaphore=new Semaphore(2);

1、当调用new Semaphore(2) 方法时,默认会创建一个非公平的锁的同步阻塞队列。

2、把初始令牌数量赋值给同步队列的state状态,state的值就代表当前所剩余的令牌数量。

初始化完成后同步队列信息如下图:

(2)获取令牌

semaphore.acquire();

1、当前线程会尝试去同步队列获取一个令牌,获取令牌的过程也就是使用原子的操作去修改同步队列的state ,获取一个令牌则修改为state=state-1。

2、 当计算出来的state<0,则代表令牌数量不足,此时会创建一个Node节点加入阻塞队列,挂起当前线程。

3、当计算出来的state>=0,则代表获取令牌成功。

源码:

/***  获取1个令牌*/public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}

/*** 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程* @param arg* @throws InterruptedException*/public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

/*** 1、创建节点,加入阻塞队列,* 2、重双向链表的head,tail节点关系,清空无效节点* 3、挂起当前节点线程* @param arg* @throws InterruptedException*/private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {//创建节点加入阻塞队列final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {//获得当前节点pre节点final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);//返回锁的stateif (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//重组双向链表,清空无效节点,挂起当前线程if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

线程1、线程2、线程3、分别调用semaphore.acquire(),整个过程队列信息变化如下图:

(3)、释放令牌

 semaphore.release();

当调用semaphore.release() 方法时

1、线程会尝试释放一个令牌,释放令牌的过程也就是把同步队列的state修改为state=state+1的过程

2、释放令牌成功之后,同时会唤醒同步队列中的一个线程。

3、被唤醒的节点会重新尝试去修改state=state-1 的操作,如果state>=0则获取令牌成功,否则重新进入阻塞队列,挂起线程。

源码:

 /*** 释放令牌*/public void release() {sync.releaseShared(1);}

/***释放共享锁,同时会唤醒同步队列中的一个线程。* @param arg* @return*/public final boolean releaseShared(int arg) {//释放共享锁if (tryReleaseShared(arg)) {//唤醒所有共享节点线程doReleaseShared();return true;}return false;}

 /*** 唤醒同步队列中的一个线程*/private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;if (ws == Node.SIGNAL) {//是否需要唤醒后继节点if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始0continue;unparkSuccessor(h);//唤醒h.nex节点线程}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE));}if (h == head)                   // loop if head changedbreak;}}

继上面的图,当我们线程1调用semaphore.release(); 时候整个流程如下图:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JDK各个版本特性讲解-JDK13特性

JDK各个版本特性讲解-JDK13特性 一、JAVA13概述二、语法层面特性1.switch表达式(预览)2.文本块(预览)2.1 概念2.2 问题2.3 目标2.4 语法细节1 基本使用2.5 语法细节2 编译器在编译时,会删除多余的空格2.6 语法细节3 转义字符2.7 语法细节4 文本块连接 三、API层次特性1.重新实现…

13、Kafka副本机制详解

Kafka 副本机制详解 1、副本定义2、副本角色3、In-sync Replicas&#xff08;ISR&#xff09;4、Unclean 领导者选举&#xff08;Unclean Leader Election&#xff09; 所谓的副本机制&#xff08;Replication&#xff09;&#xff0c;也可以称之为备份机制&#xff0c;通常是指…

制造企业为什么需要CRM系统?

在当今这个数字化、网络化的时代&#xff0c;制造业面临着越来越多的挑战。市场竞争日益激烈&#xff0c;客户的需求也变得越来越多样化、个性化。在这样的背景下&#xff0c;制造企业需要引入先进的管理工具和技术手段来提高自身的竞争力。其中&#xff0c;CRM&#xff08;客户…

为什么我的对话框创建失败了?菜鸟错误1

对话框中的资源要么被定义为一个整数&#xff0c;要么被定义为一个字符串。 仅仅一个简单的错误将会将其中的一个类型错误的变成另一个类型。我们来看一个例子。 >> 请移步至 www.topomel.com 以查看图片 << 你是否能发现其中的两处 “菜鸟级错误” ? 如果先获…

Elasticsearch:生成 AI 中的微调与 RAG

在自然语言处理 (NLP) 领域&#xff0c;出现了两种卓越的技术&#xff0c;每种技术都有其独特的功能&#xff1a;微调大型语言模型 (LLM) 和 RAG&#xff08;检索增强生成&#xff09;。 这些方法极大地影响了我们利用语言模型的方式&#xff0c;使它们更加通用和有效。 在本文…

Linux系统管理、服务器设置、安全、云数据中心

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 我们来快速了解liunx命令 文章目录 前言解析命令提示符linux的文件和目录文件和目录管理文件操作 进程管理命令系统管理网络管理 书籍推荐 本文以服务器最常用的CentOS为例 解析命令提示…

SM4系列

简述 SM4也是一个对称分组加密密钥长度&#xff1a;16bytes分组长度 密钥长度明文长度 密文长度 python自带库安装 pip install gmssl SM4加密 from gmssl import sm4key b"UKFCUKFCUKFCUKFC" strData bRe__Pointenc sm4.CryptSM4() enc.set_key(key, sm4.S…

2024年完整湖北等保测评机构名单看这里!

等保测评机构是指经公安部认证的具有资质的测评机构&#xff0c;主要从事等级测评活动。一般过等保需要找正规具有资质的等保测评机构。那你知道2024年湖北等保测评机构有哪些&#xff1f;名单有吗&#xff1f; 2024年完整湖北等保测评机构名单看这里&#xff01; 1、湖北星…

接口测试【断言设置思路】实操

1 断言设置思路 这里总结了我在项目中常用的5种断言方式&#xff0c;基本可能满足90%以上的断言场景&#xff0c;具体参见如下脑图&#xff1a; 在这里插入图片描述 下面分别解释一下图中的五种思路&#xff1a; 1&#xff09; 响应码 对于http类接口&#xff0c;有时开发人…

无损编码——Slepian-Wolf理论

在信息论中&#xff0c;无损编码是一种重要的编码技术&#xff0c;其目的是通过尽量少的比特数来表示一段信息&#xff0c;同时保证信息的完整性和准确性。传统的无损编码方法往往只考虑单个源的编码问题&#xff0c;比如哈夫曼编码和算术编码等。然而&#xff0c;在实际应用中…

RTK、PPP与RTK-PPP?一文带您认识高精定位及如何进行高精定位GNSS测试!(一)

来源&#xff1a;德思特测试测量 德思特干货丨RTK、PPP与RTK-PPP&#xff1f;一文带您认识高精定位及如何进行高精定位GNSS测试&#xff01;&#xff08;一&#xff09; 原文链接&#xff1a;https://mp.weixin.qq.com/s/6Jb3DuJEhRGqFPrH3CX8xQ 欢迎关注虹科&#xff0c;为您…

#HarmonyOS:项目结构图

.hvigor&#xff1a;存储构建配置文件信息 .idea&#xff1a;存储项目的配置信息 AppScope&#xff1a;全局的共有资源存放目录

uniapp数据缓存(存储/获取/移除/清空)

1.存储&#xff1a; 异步&#xff1a;uni.setStorage(OBJECT) uni.setStorage({key: storage_key,data: hello,success: function () {console.log(success);} });同步&#xff1a;uni.setStorageSync(KEY,DATA) try {uni.setStorageSync(storage_key, hello); } catch (e) …

大模型评估中Pass@k值是如何计算的

在前面的博客中分别介绍了大模型评估过程不同指标的含义&#xff0c;以及如何通过代码&#xff0c;实现指标的收集。如果对如何运行代码生成结果和收集passk指标不清楚&#xff0c;可以参考这两篇博客。 如何对大模型进行评估上 如何对大模型进行评估下 Passk的来源 代码的生…

day34算法训练|贪心算法

1005.K次取反后最大化的数组和 两次贪心算法思路 1. 数组中有负数时&#xff0c;把绝对值最大的负数取反 2. 数组全为非负数时&#xff0c;一直取反最小的那个数 步骤&#xff1a; 第一步&#xff1a;将数组按照绝对值大小从大到小排序&#xff0c;注意要按照绝对值的大小…

基于YOLOv8深度学习的智能小麦害虫检测识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

VMware下安装win7

参考&#xff1a; VMware下安装win7教程_vm安装win7-CSDN博客 ps:我使用的就是上面这位博主的第一个镜像。 【虚拟机安装Win7】5分钟带你学会通过VMware虚拟机安装Windows 7&#xff0c;特别是各省份专升本考试需要考查Windows 7计算机系统的宝子_哔哩哔哩_bilibili

CAS的详细介绍

CAS&#xff08;Compare and Swap&#xff09;是一种并发算法&#xff0c;通常用于解决多线程环境下的数据竞争问题。CAS的基本思想是通过在操作变量时&#xff0c;先比较当前值和期望值是否相等&#xff0c;如果相等则更新为新的值&#xff0c;否则不进行任何操作。 CAS操作包…

脚本执行权限——chmod +x、chmod -x

linux系统下&#xff0c;不同类型的文件用不同颜色表示&#xff1a; 蓝色表示目录; 绿色表示可执行文件&#xff0c;可执行的程序; 红色表示压缩文件或包文件; 浅蓝色表示链接文件; 灰色表示其它文件; 红色闪烁表示链接的文件有问题了 黄色表示设备文件 当执行chmod x test.sh…

Springboot参数校验复制即用

引入依赖 <dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version> </dependency> <dependency><groupId>org.hibernate.validator</groupI…