【Java 并发】Semaphore

Java 中 的 Semaphore (信号量) 是多线程编程中一种重要的同步工具, 用于控制对共享资源的访问。
通过 Semaphore, 我们可以限制同时访问共享资源的线程数量, 有效地管理并发访问, 确保程序在多线程环境下的稳定性和效率。
在一些资源有限制场景下, Semaphore 是特别合适的, 比如流量控制, 数据库连接池等。

1 Semaphore 的构造方法

public class Semaphore implements java.io.Serializable {public Semaphore(int permits) {// permits 同时可以有多少资源可以获取, 默认的实现为非公平实现sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) {// 参数 2 可以配置是否为公平实现sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
}

通过上面的构造函数可以看出, Semaphore 最终还是依靠 AQS 实现的, 关于 AQS 就不展开了。
核心的思想就是传入一个整数, 表示资源的数量, 同时提供了一个布尔值, 来决定资源的竞争是否按照公平原则。

2 Semaphore 的主要方法

方法名说明
void acquire() throws InterruptedException获取一个资源,如果无法获取到, 则阻塞等待直至能够获取为止
void acquire(int permits) throws InterruptedException同 acquire 方法功能基本一样, 只不过该方法可以一次性获取多个资源
void release()释放一个资源
void release(int permits)同 release 方法功能基本一样, 只不过该方法可以一次性释放多个资源
boolean tryAcquire()尝试获取一个资源, 如果能够获取成功则立即返回 true, 否则, 则返回 false
boolean tryAcquire(int permits)与 tryAcquire 方法一致, 只不过这里可以指定获取多个资源
boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException尝试获取一个资源, 如果能够立即获取到或者在指定时间内能够获取到, 则返回 true, 否则返回 false
boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException与上一个方法一致, 只不过这里能够获取多个资源
int availablePermits()返回当前可用的资源个数
int getQueueLength()返回正在等待获取资源的线程数
boolean hasQueuedThreads()是否有线程正在等待获取资源
Collection<Thread> getQueuedThreads()获取所有正在等待资源的线程集合

下面用一个简单的例子来说明 Semaphore 的具体使用。 我们来模拟这样一样场景。
有一天, 班主任需要班上 10 个同学到讲台上来填写一个表格, 但是老师只准备了 5 支笔, 因此, 只能保证同时只有 5
个同学能够拿到笔并填写表格,
没有获取到笔的同学只能够等前面的同学用完之后, 才能拿到笔去填写表格。该示例代码如下:

public class SemaphoreDemo {// 表示老师只有 10 支笔private static Semaphore semaphore = new Semaphore(5);public static void main(String[] args) {// 表示 10 个学生ExecutorService service = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {service.execute(() -> {try {System.out.println(Thread.currentThread().getName() + "  同学准备获取笔......");semaphore.acquire();System.out.println(Thread.currentThread().getName() + "  同学获取到笔");System.out.println(Thread.currentThread().getName() + "  填写表格ing.....");TimeUnit.SECONDS.sleep(3);semaphore.release();System.out.println(Thread.currentThread().getName() + "  填写完表格, 归还了笔!!!!!!");} catch (InterruptedException e) {e.printStackTrace();}});}service.shutdown();}
}

输出结果

pool-1-thread-1  同学准备获取笔......
pool-1-thread-1  同学获取到笔
pool-1-thread-1  填写表格ing.....
pool-1-thread-2  同学准备获取笔......
pool-1-thread-2  同学获取到笔
pool-1-thread-2  填写表格ing.....
pool-1-thread-3  同学准备获取笔......
pool-1-thread-4  同学准备获取笔......
pool-1-thread-3  同学获取到笔
pool-1-thread-4  同学获取到笔
pool-1-thread-4  填写表格ing.....
pool-1-thread-3  填写表格ing.....
pool-1-thread-5  同学准备获取笔......
pool-1-thread-5  同学获取到笔
pool-1-thread-5  填写表格ing.....
pool-1-thread-6  同学准备获取笔......
pool-1-thread-7  同学准备获取笔......
pool-1-thread-8  同学准备获取笔......
pool-1-thread-9  同学准备获取笔......
pool-1-thread-10  同学准备获取笔......pool-1-thread-4  填写完表格, 归还了笔!!!!!!
pool-1-thread-9  同学获取到笔
pool-1-thread-9  填写表格ing.....
pool-1-thread-5  填写完表格, 归还了笔!!!!!!
pool-1-thread-7  同学获取到笔
pool-1-thread-7  填写表格ing.....
pool-1-thread-8  同学获取到笔
pool-1-thread-8  填写表格ing.....
pool-1-thread-1  填写完表格, 归还了笔!!!!!!
pool-1-thread-6  同学获取到笔
pool-1-thread-6  填写表格ing.....
pool-1-thread-3  填写完表格, 归还了笔!!!!!!
pool-1-thread-2  填写完表格, 归还了笔!!!!!!
pool-1-thread-10  同学获取到笔
pool-1-thread-10  填写表格ing.....
pool-1-thread-7  填写完表格, 归还了笔!!!!!!
pool-1-thread-9  填写完表格, 归还了笔!!!!!!
pool-1-thread-8  填写完表格, 归还了笔!!!!!!
pool-1-thread-6  填写完表格, 归还了笔!!!!!!
pool-1-thread-10  填写完表格, 归还了笔!!!!!!

根据输出结果进行分析, Semaphore 允许的最大资源为 5, 也就是允许的最大并发执行的线程个数为 5, 可以看出, 前 5 个线程(前 5 个学生)先获取到笔, 然后填写表格。
而 6-10 这 5 个线程, 由于获取不到资源, 只能阻塞等待。当线程 pool-1-thread-4 释放了资源后, pool-1-thread-9 就可以获取到许可, 继续往下执行,
对其他线程的执行过程, 也是同样的道理。 从这个例子就可以看出, Semaphore 用来做特殊资源的并发访问控制是相当合适的, 如果有业务场景需要进行流量控制, 可以优先考虑 Semaphore。

3 Semaphore 的源码实现

Semaphore 内部是通过 AQS 的共享锁实现的, 所以只要理解了 Semaphore 的同步器, 基本就能了解大体的实现了。

3.1 Semaphore 中的 同步器

public class Semaphore implements java.io.Serializable {/*** 内部定义的同步器*/abstract static class Sync extends AbstractQueuedSynchronizer {Sync(int permits) {setState(permits);}final int nonfairTryAcquireShared(int acquires) {// 非公平的获取锁for (;;) {// 当前的状态int available = getState();// 当前的状态 - 需要的状态, 得到剩下的状态int remaining = available - acquires;// remaining 小于 0 或者通 cas 设置为新的状态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) throw new Error("Maximum permit count exceeded");// 通过 cas 设置, 成功了释放锁成功    if (compareAndSetState(current, next))return true;}}final void reducePermits(int reductions) {// 减少许可证, 即减少状态值for (;;) {// 获取当前的状态int current = getState();// 计算出新的状态值int next = current - reductions;// 新的状态值大于当前的状态值if (next > current)throw new Error("Permit count underflow");// cas 交换    if (compareAndSetState(current, next))return;}}final int drainPermits() {// 将当前的状态值设置为 0 for (;;) {// 获取当前的状态值int current = getState();// 当前的状态值等于 0 了// 或者通过 cas 将当前的状态值设置为 0if (current == 0 || compareAndSetState(current, 0))return current;}}}/*** 非公平锁的实现* 很简单, 全部都是直接复用 Sync 的方法*/static final class NonfairSync extends Sync {NonfairSync(int permits) {super(permits);}protected int tryAcquireShared(int acquires) {return nonfairTryAcquireShared(acquires);}}/*** 公平锁的实现*/static final class FairSync extends Sync {// 尝试获取共享锁// 这里是公平锁的实现, 而非公平锁的实现, 就是直接调用 nonfairTryAcquireShared 方法// FairSync 和 NonfairSync 只是在 Sync 的基础上重写了这个方法, 没有其他的改变了protected int tryAcquireShared(int acquires) {for (;;) {// 调用 AbstractQueuedSynchronizer 的 hasQueuedPredecessors 方法, // 判断当前同步队列中是否有符合条件的候选节点, 即同步队列中有没有状态不是取消状态的节点, // 有的话, 返回 -1, 尝试获取锁失败if (hasQueuedPredecessors())return -1;// 获取可用的状态    int available = getState();// 可用的状态 - 需要的状态, 得到剩下的状态int remaining = available - acquires;// 如果剩余的状态小于 0 了 获取通过 cas 设置新的状态失败if (remaining < 0 || compareAndSetState(available, remaining))// 返回剩余的状态return remaining;}}}}

3.2 Semaphore 的 acquire 方法

public class Semaphore implements java.io.Serializable {public void acquire() throws InterruptedException {// 1. 先调用到 AbstractQueuedSynchronizer 的 acquireSharedInterruptibly// 2. 在 AQS 的 acquireSharedInterruptibly 中先通过 Semaphore 自定义的 Sync 的 tryAcquireShared() 方法判断是否可以获取锁// 在 tryAcquireShared 方法获取当前的状态值, 通过当前的状态值 - 需要获取的状态值, 得到剩余的状态值// 如果剩余的状态值小于 0, 否则通过 cas 交换当前的状态值为剩余值// 最后返回剩余值// 3. 获取锁失败后, 会加入同步队列, 等待唤醒sync.acquireSharedInterruptibly(1);}
}

3.3 Semaphore 的 release 方法

public class Semaphore implements java.io.Serializable {public void release() {// 1. 先调用到 AbstractQueuedSynchronizer 的 releaseShared// 2. 在 AQS 的 releaseShared 中先通过 CountDownLatch 自定义的 Sync 的 tryReleaseShared() 方法判断是否可以释放锁// 在 tryReleaseShared 方法中, 获取到当前的状态值, 当前的状态值 + 释放的状态值, 得到最新的状态值// 通过 cas 设置当前的状态值为最新的状态值, 释放锁成功sync.releaseShared(1);}
}

几乎所有的方法都是基于同步器 AQS 实现的, 所有理解了 AQS 的实现, Semaphore 的实现也就不难理解了。
至于其他的方法, 比如 tryAcquire, tryRelease, availablePermits 等方法也都是同样的思想, 这里就不再赘述了。

4 参考

大白话说Java并发工具类-Semaphore, Exchanger

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

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

相关文章

C++感受1-打开浏览器,线上玩转C++

介绍了五款在线编译、编译、运行的C线上环境。并选择其中的 “在线GDB” 网站动手编写、运行第一个C程序 “Hello World”&#xff0c;同时和线下IDE进行对比。 1. 课堂视频 打开浏览器&#xff0c;线上玩转C 2. 在线C编译环境对比 onlinegdb &#xff1a; www.onlinegdb.comr…

汽车大灯汽车尾灯破裂裂纹破损破洞掉角崩角等问题能修复吗?修复灯罩需要多久时间?

汽车大灯汽车尾灯破裂裂纹破损破洞掉角崩角等问题基本是可以修复的。 修复汽车灯罩的时间取决于多个因素&#xff0c;如灯罩的破损程度、修复方法的选择以及维修店的工作效率等。 一般来说&#xff0c;如果灯罩的破损程度较轻&#xff0c;仅需要进行简单的修复或翻新&#xf…

Python常用语法汇总(三):函数、类

9. 函数 #例1def greet_user(username): # 定义函数 """显示简单的问候语""" print(Hello, username.title() !) greet_user(Jesse) # 调用函数 #例2 def get_formatted_name(first_name,last_name): """返回整洁的姓名&q…

如何考上东南大学计算机学院?

东南大学招生学院是计算机科学与工程学院、苏州联合研究生院&#xff0c;复试公平&#xff0c;不歧视双非考生&#xff0c;985院校中性价比较高&#xff0c;但近年热度在逐年上涨&#xff0c;需要警惕。 建议报考计算机科学与工程学院081200计算机科学与技术专业目标分数为380…

帮管客CRM(jiliyu)接口SQL注入漏洞

文章目录 前言声明一、漏洞描述二、影响版本三、漏洞复现四、修复建议 前言 帮管客CRM客户管理系统专注于为企业提供crm客户关系管理、crm管理系统、crm软件产品及企业销售管理流程解决方案服务,助力企业业绩增长。 声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由…

jdk17出现错误无法初始化主类 和NoClassDefFoundError:Vector的解决方法

概述&#xff1a;网上流传文章大多都是编译和运行都加下面这串代码 --add-modulesjdk.incubator.vector我估计他们大多都是复制粘贴的文章&#xff0c;这种东西就是电子垃圾&#xff0c;在idea中&#xff0c;大多人都习惯用maven来构建java项目&#xff0c;接下来我将讲解使用…

倒计时34,33天

一&#xff1a; 背包问题复习&#xff1a; //01背包&#xff1a; #include<bits/stdc.h> using namespace std; #define int long long const int N2e56; int v[N],w[N],dp[N]; const int inf0x3f3f3f3f; void solve() {int n,V;cin>>n>>V;for(int i1;i<…

【C++】string学习 — 手搓string类项目

手搓string项目 1 string类介绍2 功能描述3 代码实现3.0 基础框架3.1 构造函数 和 析构函数3.2 流操作符重载 和 尾插扩容3.4 运算符重载3.5 实用功能3.6 迭代器模拟 总结Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇…

LeetCode:206.反转链表

206. 反转链表 解题过程 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; …

变更接口形参,但是不想影响接口调用者怎么办?

例如接口&#xff1a;public void test(String s1){}&#xff0c;该接口有多个生产者即调用者。 因为其中一个生产者需求变动&#xff0c;调用test方法的时候需要新增一个参数。这种情况下如果直接修改test方法增加参数&#xff0c;需要修改所有的调用者代码&#xff0c;比较麻…

linux下目录切换快捷指令

cd - "cd -" 是一个命令行中的快捷方式&#xff0c;用于返回上一个工作目录。通常&#xff0c;当你在命令行中使用 cd 命令切换目录时&#xff0c;系统会记住你之前所在的目录。通过输入 cd -&#xff0c;你可以返回到上一个目录&#xff0c;而不必输入完整的路径。…

如何使用 Langchain、Ollama 和 Streamlit 构建 RAG

一、先决条件&#xff1a;您需要了解什么 在深入讨论技术细节之前&#xff0c;我们先概述一下先决条件。Python 的基础知识至关重要&#xff0c;因为它是我们将使用的主要语言。熟悉机器学习和自然语言处理的基本概念将帮助您更轻松地掌握这些概念。此外&#xff0c;对 Langch…

蓝桥杯练习系统(算法训练)ALGO-973 唯一的傻子

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 腿铮找2255有点事&#xff0c;但2255太丑了&#xff0c;所以腿铮不知道他的长相。正愁不知道到如何找他的时候&#xff0c;…

TCP通信程序

#发端 #include "head.h"int main(void) { int sockfd 0;ssize_t nsize 0;struct sockaddr_in recvaddr;char tmpbuff[1024];int ret 0;int fd 0; int nret 0;sockfd socket(AF_INET,SOCK_STREAM,0); /* 流式套接字 */if(-1sockfd){perror("fail to socke…

【开发环境】Ubuntu 18.04 搭建 QT编译环境详细步骤 【亲测有效】

目录 1 查看Ubuntu系统中Qt版本 2 下载Ubuntu系统Qt版本安装包 3 Qt安装 3.1 Qt 安装步骤 3.2 安装qt发现Ubuntu空间不足&#xff0c;怎么去扩容呢&#xff1f; 3.2.1 硬盘操作步骤&#xff08;需要关闭虚拟机进行操作&#xff09; 3.2.2 Ubuntu命令操作&#xff1a;安装…

云计算项目七:jump-server安装部署

jump-server安装部署 配置清单 jumpserver概述 Jumpserver是一款开源的堡垒机&#xff0c;可使系统的管理员和开发人员安全的连接到企业内部服务器上执行操作&#xff0c;并且支持大部分操作系统&#xff0c;是一款非常安全的远程连接工具 常见支持的系统 CentOS, RedHat, …

基于springboot实现酒店客房管理系统项目【项目源码+论文说明】

基于springboot实现酒店客房管理平台系统演示 摘 要 随着人们的物质水平的提高&#xff0c;旅游业和酒店业发展的速度越来越快。近年来&#xff0c;市面上酒店的数量和规模都在不断增加&#xff0c;如何提高酒店的管理效率和服务质量成为了一个重要的问题。伴随着信息技术的发…

内网渗透-跨域环境渗透-1

目录 smbclient工具 mimikatz工具 Kerbers协议 NTLM认证 hash传递攻击&#xff08;PTH攻击&#xff09; 黄金票据攻击 白银票据 MS14-068 smbclient工具 在linux里面连接远程windows共享目录&#xff0c;可以使用这个工具 ​ 第一种连接方式&#xff1a;smbclient -L 目…

HarmonyOS 非线性容器特性及使用场景

非线性容器实现能快速查找的数据结构&#xff0c;其底层通过 hash 或者红黑树实现&#xff0c;包括 HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet、PlainArray 七种。非线性容器中的 key 及 value 的类型均满足 ECMA 标准。 HashMap HashMap 可用来存…

牛客网KY19 今年的第几天?

题目 描述&#xff1a; 输入年、月、日&#xff0c;计算该天是本年的第几天。 输入描述&#xff1a; 包括三个整数年(1<Y<3000)、月(1<M<12)、日(1<D<31)。 输出描述&#xff1a; 输入可能有多组测试数据&#xff0c;对于每一组测试数据&#xff0c; 输出一个…