Semaphore 信号量

文章目录

    • 基本概念
    • 工作原理
    • Semaphore 与 ReentrantLock
    • Semaphore常用场景
      • 1. 限制并发线程数(最常见场景)
      • 2. 公平模式的信号量(保证按顺序访问资源)
      • 3. 限制数据库连接数(模拟数据库连接池)
      • 4. 限制 API 请求次数

基本概念

在并发编程中,Semaphore(信号量)是一种用于控制访问共享资源的工具。它通过内部计数器来实现对资源的访问控制,可以控制同时访问特定资源的线程数量。Semaphore 可以用于限制并发访问的线程数量,常用于生产者-消费者问题、连接池控制等场景。
Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore 可以用来构建一些对象池,资源池之类的,比如数据库连接池
Java 中的 Semaphore 是一种新的同步类,它是一个计数信号。从概念上讲, 从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每 一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可 能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只 对可用许可的号码进行计数,并采取相应的行动。信号量常常用于多线程的代 码中,比如数据库连接池
实现互斥锁(计数器为 1),我们也可以创建计数为 1 的 Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
Semaphore 是一种强大的并发控制工具,广泛用于限制并发线程数、控制共享资源的访问以及解决生产者-消费者问题。通过合理配置许可数和选择公平或非公平模式,可以根据应用的不同需求进行灵活的并发控制。

信号量(Semaphore) 是一种同步工具,通常用于限制并发访问资源的数量。
信号量内部持有一个计数器,该计数器表示可以允许多少个线程同时访问某个共享资源。
每当一个线程尝试访问资源时,信号量的计数器会减一;当线程访问完毕后,信号量的计数器会加一,表示一个资源位置被释放。

工作原理

获取资源 (acquire()):如果信号量的计数器大于 0,线程可以获取资源,计数器减一。如果计数器为 0,线程就会被阻塞,直到其他线程释放资源。
释放资源 (release()):当线程访问完共享资源后,调用 release() 方法,信号量的计数器加一,释放一个资源位置。如果有线程因为信号量的计数器为 0 而被阻塞,释放信号量会唤醒其中一个线程。
Semaphore 类构造函数

public class Semaphore {// 构造函数public Semaphore(int permits) {// 创建一个信号量,初始许可数为 permits}public Semaphore(int permits, boolean fair) {// 创建一个公平信号量,初始许可数为 permits}
}

permits:表示信号量的许可数,即可以同时允许多少个线程访问资源。
fair:如果设置为 true,表示公平模式,线程将按请求的顺序获取许可。默认情况下是 false,即不保证线程获取许可的顺序。
常用方法

  1. acquire()
    描述:用于获取一个许可。如果信号量的计数器大于 0,许可数减 1;如果计数器为 0,线程会被阻塞,直到有线程释放许可。
    用法:
    semaphore.acquire(); // 获取一个许可
    抛出异常:InterruptedException 如果线程在等待许可的过程中被中断。
  2. release()
    描述:释放一个许可,信号量的计数器加 1。如果有线程因为获取许可而被阻塞,释放许可时会唤醒一个线程。
    用法:
    semaphore.release(); // 释放一个许可
  3. availablePermits()
    描述:返回当前信号量的许可数,即信号量当前可用的资源数量。
    用法:
    int available = semaphore.availablePermits(); // 获取当前可用的许可数
  4. tryAcquire()(非阻塞获取)
    描述:尝试获取一个许可。如果信号量的计数器大于 0,许可数减 1,方法返回 true。如果计数器为 0,方法立即返回 false,不阻塞线程。
    用法:
    boolean acquired = semaphore.tryAcquire(); // 非阻塞获取许可
  5. tryAcquire(long timeout, TimeUnit unit)(带超时的非阻塞获取)
    描述:尝试在指定的时间内获取一个许可。如果在超时时间内获取成功,返回 true;如果超时未成功获取,则返回 false。
    用法:
    boolean acquired = semaphore.tryAcquire(100, TimeUnit.MILLISECONDS); // 100毫秒内尝试获取许可
    示例:使用 Semaphore 控制并发
  6. 控制并发线程数
    假设你有一个任务需要控制最大并发数量,可以使用 Semaphore 来限制同时执行任务的线程数。
import java.util.concurrent.Semaphore;public class SemaphoreExample {private static Semaphore semaphore = new Semaphore(3);  // 最多允许3个线程同时执行public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(new Task(i)).start();}}static class Task implements Runnable {private int taskId;public Task(int taskId) {this.taskId = taskId;}@Overridepublic void run() {try {semaphore.acquire();  // 获取许可System.out.println("Task " + taskId + " is executing.");Thread.sleep(2000);  // 模拟任务执行时间System.out.println("Task " + taskId + " finished.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {semaphore.release();  // 释放许可}}}
}

这里创建了一个 Semaphore 对象,允许最多 3 个线程同时执行任务。如果有更多的线程,它们将被阻塞,直到其他线程释放许可。
2. 公平信号量
Semaphore semaphore = new Semaphore(1, true); // 公平模式,允许一个线程访问资源
当多个线程请求访问资源时,公平模式保证线程按请求顺序获取许可,而不公平模式则可能导致一些线程长时间无法获取许可(例如,后来的线程可能在前面的线程之前获得许可)。
应用场景
限流:可以控制一个系统在某个时间内并发访问的最大数量。例如,限制同一时刻只能有 5 个请求访问某个外部服务。
连接池:控制同时可以与外部系统建立连接的线程数。连接池中每个连接都可以视为一个资源。
并发控制:在多个线程访问共享资源时,控制同时执行的线程数量,防止过多线程争夺资源造成性能瓶颈或资源耗尽。
生产者-消费者问题:在生产者生成数据的速度和消费者处理数据的速度不一致时,信号量可以用来控制生产者和消费者之间的协作。
注意事项
公平 vs 非公平:默认情况下,Semaphore 是非公平的。在高并发环境下,公平信号量(fair=true)可能会引入一些额外的性能开销,因为它保证了线程获取资源的顺序。
死锁:和其他并发工具一样,不正确的使用信号量可能导致死锁。例如,如果一个线程在获取信号量后发生异常而没有释放信号量,可能会导致其他线程一直等待资源。
性能开销:虽然信号量是轻量级的,但是在高并发情况下频繁地获取和释放许可可能会带来一定的性能开销,尤其是在公平模式下。

Semaphore 与 ReentrantLock

Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似,通过 acquire()与release()方法来获得和释放临界资源。经实测,Semaphone.acquire()方法默认为可响应中断锁,
与 ReentrantLock.lockInterruptibly()作用效果一致,也就是说在等待临界资源的过程中可以被
Thread.interrupt()方法中断。
此外,Semaphore 也实现了可轮询的锁请求与定时锁的功能,除了方法名 tryAcquire 与 tryLock不同,其使用方法与 ReentrantLock 几乎一致。Semaphore 也提供了公平与非公平锁的机制,也可在构造函数中进行设定。
Semaphore 的锁释放操作也由手动进行,因此与 ReentrantLock 一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在 finally 代码块中完成。
// 创建一个计数阈值为 5 的信号量对象

// 只能 5 个线程同时访问
Semaphore semp = new Semaphore(5);
try { // 申请许可
semp.acquire();
try {
// 业务逻辑} catch (Exception e) {
} finally {
// 释放许可
semp.release();
}
} catch (InterruptedException e) {
}

Semaphore常用场景

Semaphore 是 Java 中用于控制同时访问特定资源的线程数的工具。它通常用于实现并发控制,避免过多的线程同时访问共享资源,导致资源过载或系统性能下降。

1. 限制并发线程数(最常见场景)

在这个示例中,我们使用 Semaphore 来限制同时访问某个资源的线程数。假设我们有一个可以并发处理请求的服务器,但我们希望限制并发的请求数量,以避免系统过载。
代码示例:限制最大并发数

import java.util.concurrent.Semaphore;public class SemaphoreExample {// 创建信号量,最多允许 3 个线程并发执行private static Semaphore semaphore = new Semaphore(3);public static void main(String[] args) {// 启动 10 个线程模拟并发请求for (int i = 0; i < 10; i++) {new Thread(new Task(i)).start();}}static class Task implements Runnable {private int taskId;public Task(int taskId) {this.taskId = taskId;}@Overridepublic void run() {try {// 获取信号量许可,最多允许 3 个线程同时执行semaphore.acquire();System.out.println("Task " + taskId + " is executing.");Thread.sleep(2000);  // 模拟任务执行时间System.out.println("Task " + taskId + " finished.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 释放信号量许可semaphore.release();}}}
}

说明
创建了一个 Semaphore 对象,最大许可数为 3,即最多允许 3 个线程同时执行任务。
每个线程执行任务时,首先调用 semaphore.acquire() 获取许可。如果没有许可可用,线程会被阻塞。
任务执行完成后,调用 semaphore.release() 释放许可,允许其他被阻塞的线程获取许可。

2. 公平模式的信号量(保证按顺序访问资源)

在默认情况下,Semaphore 是非公平的,即线程获取许可的顺序是不确定的。为了保证线程按照请求的顺序来获取许可,可以使用公平模式。
代码示例:公平信号量

import java.util.concurrent.Semaphore;public class FairSemaphoreExample {// 创建公平信号量,最多允许 2 个线程并发执行private static Semaphore semaphore = new Semaphore(2, true);public static void main(String[] args) {// 启动 5 个线程模拟并发请求for (int i = 0; i < 5; i++) {new Thread(new Task(i)).start();}}static class Task implements Runnable {private int taskId;public Task(int taskId) {this.taskId = taskId;}@Overridepublic void run() {try {// 获取信号量许可semaphore.acquire();System.out.println("Task " + taskId + " is executing.");Thread.sleep(2000);  // 模拟任务执行时间System.out.println("Task " + taskId + " finished.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 释放信号量许可semaphore.release();}}}
}

说明
在创建 Semaphore 时,第二个参数设置为 true,表示启用公平模式。在这种模式下,信号量会保证线程按顺序(即按照线程请求的顺序)获取许可。
如果不使用公平模式,则线程获得许可的顺序可能不按请求顺序,而是依赖于操作系统的调度策略。

3. 限制数据库连接数(模拟数据库连接池)

在多线程环境下,假设多个线程需要访问数据库,但为了避免数据库过载,我们限制同一时刻只有一定数量的线程能访问数据库。这个场景很适合用 Semaphore 来模拟连接池的并发控制。
代码示例:数据库连接池

import java.util.concurrent.Semaphore;public class DatabaseConnectionPool {// 创建信号量,模拟连接池,最多允许 3 个线程同时访问数据库private static Semaphore connectionPool = new Semaphore(3);public static void main(String[] args) {// 启动 10 个线程模拟并发数据库访问for (int i = 0; i < 10; i++) {new Thread(new DatabaseTask(i)).start();}}static class DatabaseTask implements Runnable {private int taskId;public DatabaseTask(int taskId) {this.taskId = taskId;}@Overridepublic void run() {try {System.out.println("Task " + taskId + " is waiting for a database connection.");// 获取一个数据库连接(信号量许可)connectionPool.acquire();System.out.println("Task " + taskId + " is using a database connection.");Thread.sleep(2000);  // 模拟数据库操作时间System.out.println("Task " + taskId + " finished using the database connection.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 释放数据库连接(释放信号量许可)connectionPool.release();}}}
}

说明
该示例模拟了一个数据库连接池,最多允许 3 个线程同时获取数据库连接(最多 3 个线程访问数据库)。
线程在获取数据库连接前会调用 connectionPool.acquire(),如果没有空闲连接,线程会被阻塞。
线程操作完数据库后,调用 connectionPool.release() 释放连接,允许其他等待的线程获取连接。

4. 限制 API 请求次数

在一些场景下,例如 API 请求的频率限制,你可能希望在一定时间内只允许有限的请求数。Semaphore 可以很好地实现这一功能。
代码示例:API 请求频率限制

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;public class ApiRateLimiter {// 创建一个信号量,最多允许每秒 2 次 API 请求private static Semaphore semaphore = new Semaphore(2);public static void main(String[] args) {// 启动 5 个线程模拟 API 请求for (int i = 0; i < 5; i++) {new Thread(new ApiRequest(i)).start();}}static class ApiRequest implements Runnable {private int requestId;public ApiRequest(int requestId) {this.requestId = requestId;}@Overridepublic void run() {try {// 尝试获取许可if (semaphore.tryAcquire(1, TimeUnit.SECONDS)) {System.out.println("API request " + requestId + " is being processed.");Thread.sleep(500);  // 模拟请求处理时间System.out.println("API request " + requestId + " finished.");} else {System.out.println("API request " + requestId + " failed due to rate limit.");}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 释放许可semaphore.release();}}}
}

说明
这个示例模拟了一个简单的 API 请求频率限制,每秒最多允许 2 个请求。
线程通过 semaphore.tryAcquire(1, TimeUnit.SECONDS) 尝试获取许可,超过频率限制的请求将失败,tryAcquire() 方法在等待超过指定时间后返回 false。
总结
通过 Semaphore 可以有效地控制并发访问的数量,适用于多种场景,包括限流、数据库连接池、资源访问控制等。在使用时需要注意合理的信号量数量设置,并且避免死锁和资源泄露等问题。

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

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

相关文章

Redis 的代理类注入失败,连不上 redis

在测试 redis 是否成功连接时&#xff0c;发现 bean 没有被创建成功&#xff0c;导致报错 根据报错提示&#xff0c;需要我们添加依赖&#xff1a; <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>&l…

桌面怎么快速添加便签?适合桌面记事的便签小工具

在数字化时代&#xff0c;我们每天面对电脑处理大量任务&#xff0c;无论是工作计划、会议纪要还是个人生活琐事&#xff0c;都需要一个可靠的桌面记事工具来帮助我们记录和整理。因此&#xff0c;一款适合桌面使用的便签软件成为了我们不可或缺的助手。 敬业签就是这样一款功…

UE5 腿部IK 解决方案 footplacement

UE5系列文章目录 文章目录 UE5系列文章目录前言一、FootPlacement 是什么&#xff1f;二、具体实现 前言 在Unreal Engine 5 (UE5) 中&#xff0c;腿部IK&#xff08;Inverse Kinematics&#xff0c;逆向运动学&#xff09;是一个重要的动画技术&#xff0c;用于实现角色脚部准…

KLV6008固态继电器:高压应用的理想紧凑方案

在当今快节奏的电子领域&#xff0c;找到平衡性能、可靠性和安全性的组件至关重要。CRIA Semiconductor的KLV6008固态继电器(SSR)正是满足了这一要求。这款紧凑型继电器专为高压、低电流切换而设计&#xff0c;是适用于各种应用的多功能解决方案。 为什么选择KLV6008&#xff1…

在 Swift 中实现字符串分割问题:以字典中的单词构造句子

文章目录 前言摘要描述题解答案题解代码题解代码分析示例测试及结果时间复杂度空间复杂度总结 前言 本题由于没有合适答案为以往遗留问题&#xff0c;最近有时间将以往遗留问题一一完善。 LeetCode - #140 单词拆分 II 不积跬步&#xff0c;无以至千里&#xff1b;不积小流&…

HarmonyOs鸿蒙开发实战(21)=>组件间通信@ohos/liveeventbus

1.简介 LiveEventBus是一款消息总线&#xff0c;具有生命周期感知能力&#xff0c;支持Sticky&#xff0c;支持跨进程&#xff0c;支持跨APP发送消息。 2.下载安装 ohpm install ohos/liveeventbus 3.订阅&#xff0c;注册监听 4.发送事件 5. 完成 > 记得关注博主&#xff…

OpenCV和Qt坐标系不一致问题

“ OpenCV和QT坐标系导致绘图精度下降问题。” OpenCV和Qt常用的坐标系都是笛卡尔坐标系&#xff0c;但是细微处有些不同。 01 — OpenCV坐标系 OpenCV是图像处理库&#xff0c;是以图像像素为一个坐标位置&#xff0c;即一个像素对应一个坐标&#xff0c;所以其坐标系也叫图像…

nohup java -jar supporterSys.jar --spring.profiles.active=prod

文章目录 1、ps -ef | grep java2、kill 13713、ps -ef | grep java4、nohup java -jar supporterSys.jar --spring.profiles.activeprod &5、ps -ef | grep java1. 启动方式进程 1371进程 19994 2. 主要区别3. 可能的原因4. 建议 1、ps -ef | grep java rootshipper:~# p…

Ubuntu上安装MySQL并且实现远程登录

目录 下载网络工具 查看网络连接 更新系统软件包&#xff1b; 安装mysql数据库 查看mysql数据库状态 以数字ip形式显示mysql的监听状态。&#xff08;默认监听端口是3306&#xff09; 查看安装mysql数据库时系统创建的目录信息。 根据查询到的系统用户名以及随机密码&a…

shell编写——脚本传参与运算

shell编写——脚本传参与运算 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本…

设计模式之 观察者模式

观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听一个主题对象&#xff08;Subject&#xff09;。当主题对象的状态发生变化时&#xff0c;所有依赖于它的观察者都会得到…

深入了解 Linux htop 命令:功能、用法与示例

文章目录 深入了解 Linux htop 命令&#xff1a;功能、用法与示例什么是 htop&#xff1f;htop 的安装htop的基本功能A区&#xff1a;系统资源使用情况B区&#xff1a;系统概览信息C区&#xff1a;进程列表D区&#xff1a;功能键快捷方式 与 top 的对比常见用法与示例实际场景应…

【深度学习】【RKNN】【C++】模型转化、环境搭建以及模型部署的详细教程

【深度学习】【RKNN】【C】模型转化、环境搭建以及模型部署的详细教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【RKNN】【C】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转rknnpytorch转onnxonnx转rkn…

【spark】远程debug spark任务(含有pyspark)

--master yarn和--master client都是可以的。 spark-submit \ --master yarn \ --deploy-mode client \ --name "test-remote-debug" \ --conf "spark.driver.extraJavaOptions-agentlib:jdwptransportdt_socket,servery,suspendn,address5005" \ --conf …

如何使用 Vivado 从源码构建 Infinite-ISP FPGA 项目

如约介绍源码构建 Infinite-ISP 项目&#xff0c;其实大家等的是源码&#xff0c;所以中间过程简洁略过&#xff0c;可以直接翻到文末获取链接。 开源ISP&#xff08;Infinite-ISP&#xff09;介绍 构建工程 第一步&#xff0c;从文末或者下面链接获取源码 https://github.com/…

彻底理解Redis的持久化方式

一.由来 因为Redis之所以能够提供高效读写的操作&#xff0c;是因为它是基于内存的&#xff0c;但是这样也会带来一个问题&#xff0c;及在服务器宕机或者重启的情况下&#xff0c;内存里面的数据就会被丢失掉&#xff0c;所以为了解决这个问题&#xff0c;Redis就提供了持久化…

Bug Fix 20241122:缺少lib文件错误

今天有朋友提醒才突然发现 gitee 上传的代码存在两个很严重&#xff0c;同时也很低级的错误。 因为gitee的默认设置不允许二进制文件的提交&#xff0c; 所以PH47框架下的库文件&#xff08;各逻辑层的库文件&#xff09;&#xff0c;以及Stm32Cube驱动的库文件都没上传到Gi…

NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案

EasyNVR是基于端-边-云一体化架构的安防监控视频融合云平台&#xff0c;具有简单轻量的部署方式与多样的功能&#xff0c;支持多种协议&#xff08;如GB28181、RTSP、Onvif、RTMP&#xff09;和设备类型&#xff08;IPC、NVR等&#xff09;&#xff0c;提供视频直播、录像、回放…

微服务架构:10个实用设计模式

1 微服务架构 微服务架构的重要特征 微服务架构的优点 微服务架构的缺点 何时使用微服务架构 2 微服务架构的设计模式 独享数据库&#xff08;Database per Microservice&#xff09; 事件源&#xff08;Event Sourcing&#xff09; 命令和查询职责分离&#xff08;CQRS&…

华为欧拉系统使用U盘制作引导安装华为欧拉操作系统

今天记录一下通过U盘来安装华为欧拉操作系统 华为欧拉操作系统是国产的一个类似于Centos的Linus系统 具体实现操作步骤&#xff1a; 先在官网下载欧拉系统镜像点击跳转到下载 准备好一个大于16g的U盘 &#xff0c;用于制作U盘启动 下载一个引导程序制作工具&#xff0c;我使用…