并发设计模式实战系列(17):信号量(Semaphore)
🌟 大家好,我是摘星! 🌟
今天为大家带来的是并发设计模式实战系列,第十七章信号量(Semaphore),废话不多说直接开始~
目录
一、核心原理深度拆解
1. 信号量本质模型
2. 并发控制三要素
二、生活化类比:停车场管理系统
三、Java代码实现(生产级Demo)
1. 完整可运行代码
2. 关键配置说明
四、横向对比表格
1. 并发控制工具对比
2. 信号量使用策略对比
五、高级应用技巧
1. 动态调整许可数
2. 信号量监控
3. 与线程池结合
六、信号量的底层实现原理
1. AQS(AbstractQueuedSynchronizer)的协作
2. 关键方法源码片段
七、生产环境中的陷阱与解决方案
1. 典型问题清单
2. 调试技巧
八、与其他模式的组合应用
1. 信号量+线程池(流量整形)
2. 信号量+CountDownLatch(阶段控制)
九、性能优化指南
1. 许可数计算公式
2. 不同场景下的参数建议
十、扩展变体实现
1. 可动态调整的信号量
2. 超时自动释放信号量
十一、行业应用案例
1. Kafka的吞吐控制
2. Tomcat连接器配置
一、核心原理深度拆解
1. 信号量本质模型
┌───────────────┐ ┌───────────────┐
│ Resource │───┬──>│ Semaphore │
│ Pool │ │ │ (计数器+队列) │
└───────────────┘ │ └───────────────┘│
┌───────────────┐ │
│ Thread │<──┘
│ Request │
└───────────────┘
- 许可证机制:内部维护一个虚拟的许可计数器(permits)
- 双原子操作:
-
acquire()
:许可-1(当>0时立即返回,=0时线程阻塞)release()
:许可+1(唤醒等待队列中的线程)
- 公平性选择:支持FIFO队列或非公平竞争
2. 并发控制三要素
- 资源总量:
new Semaphore(N)
初始化许可数 - 占用规则:
tryAcquire(timeout)
防止死锁 - 释放保证:必须放在finally块中执行
二、生活化类比:停车场管理系统
系统组件 | 现实类比 | 核心行为 |
Semaphore | 剩余车位显示屏 | 显示可用车位数量 |
acquire() | 车辆进入抬杆 | 占用车位(数量-1) |
release() | 车辆离开 | 释放车位(数量+1) |
等待队列 | 入口排队车辆 | 按到达顺序或抢车位 |
- 突发流量:当100辆车同时到达50个车位的停车场时:
-
- 前50辆立即进入
- 后50辆需等待前车离开
三、Java代码实现(生产级Demo)
1. 完整可运行代码
import java.util.concurrent.*;
import java.util.concurrent.locks.*;public class SemaphoreDemo {// 数据库连接池实现static class ConnectionPool {private final Semaphore semaphore;private final BlockingQueue<Connection> pool;public ConnectionPool(int poolSize) {this.semaphore = new Semaphore(poolSize, true); // 公平模式this.pool = new LinkedBlockingQueue<>(poolSize);for (int i = 0; i < poolSize; i++) {pool.add(new Connection("Conn-" + i));}}public Connection getConnection() throws InterruptedException {semaphore.acquire(); // 如果没有许可则阻塞return pool.take();}public void releaseConnection(Connection conn) {pool.offer(conn);semaphore.release(); // 释放许可}}static class Connection {private String name;public Connection(String name) { this.name = name; }@Overridepublic String toString() { return name; }}// 模拟业务操作public static void main(String[] args) {final ConnectionPool pool = new ConnectionPool(3);ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executor.execute(() -> {try {Connection conn = pool.getConnection();System.out.println(Thread.currentThread().getName() + " 获取连接: " + conn);// 模拟业务操作Thread.sleep(1000);pool.releaseConnection(conn);System.out.println(Thread.currentThread().getName() + " 释放连接: " + conn);} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown();}
}
2. 关键配置说明
// 创建信号量(公平模式 vs 非公平模式)
new Semaphore(permits, true); // 超时获取许可(避免死锁)
semaphore.tryAcquire(2, TimeUnit.SECONDS);// 一次性获取多个许可
semaphore.acquire(3); // 需要3个许可才能继续
四、横向对比表格
1. 并发控制工具对比
工具 | 特点 | 适用场景 |
Semaphore | 控制资源访问数量 | 连接池、限流 |
CountDownLatch | 一次性栅栏 | 多线程任务汇总 |
CyclicBarrier | 可重复使用的栅栏 | 多阶段并行计算 |
ReentrantLock | 独占锁 | 临界区精细控制 |
2. 信号量使用策略对比
策略 | 优点 | 缺点 |
公平模式 | 避免线程饥饿 | 吞吐量较低 |
非公平模式 | 吞吐量高 | 可能造成线程饥饿 |
多许可申请 | 支持复杂资源分配 | 容易导致死锁 |
可中断获取 | 响应线程中断 | 需要处理中断异常 |
五、高级应用技巧
1. 动态调整许可数
// 动态扩容(JDK没有直接方法,需通过包装实现)
class ResizableSemaphore {private final ReentrantLock lock = new ReentrantLock();private Semaphore semaphore;public ResizableSemaphore(int permits) {this.semaphore = new Semaphore(permits);}public void setPermits(int newPermits) {lock.lock();try {int delta = newPermits - semaphore.availablePermits();if (delta > 0) {semaphore.release(delta); // 增加许可} else {semaphore.reducePermits(-delta); // 减少许可}} finally {lock.unlock();}}
}
2. 信号量监控
// 监控关键指标
int availablePermits = semaphore.availablePermits();
int queueLength = semaphore.getQueueLength(); // 等待线程数
3. 与线程池结合
// 使用信号量限制任务提交速率
ExecutorService executor = Executors.newCachedThreadPool();
Semaphore rateLimiter = new Semaphore(10); // 最大10并发executor.execute(() -> {rateLimiter.acquire();try {// 执行任务...} finally {rateLimiter.release();}
});
好的!我将延续原有结构,从 第六部分 开始扩展信号量(Semaphore)的高级特性和工程实践细节。
六、信号量的底层实现原理
1. AQS(AbstractQueuedSynchronizer)的协作
┌───────────────────┐
│ Semaphore │
│ (Sync继承AQS) │
│ - state=permits │
│ - 共享模式 │
└─────────┬─────────┘│
┌─────────▼─────────┐
│ NonFairSync │ 或 │ FairSync │
│ - 直接竞争许可 │ │ - FIFO队列 │
└───────────────────┘
- state字段:存储当前可用许可数(volatile修饰)
- 共享模式:与ReentrantLock(独占模式)的核心区别
- 非公平实现:
NonFairSync.tryAcquireShared()
允许插队 - 公平实现:
FairSync.tryAcquireShared()
检查是否有等待队列
2. 关键方法源码片段
// JDK 17中的非公平获取逻辑
final int nonfairTryAcquireShared(int acquires) {for (;;) {int available = getState();int remaining = available - acquires;if (remaining < 0 || compareAndSetState(available, remaining)) {return remaining; // 负数表示获取失败}}
}
七、生产环境中的陷阱与解决方案
1. 典型问题清单
问题类型 | 现象 | 解决方案 |
许可泄漏 | 可用许可逐渐减少 | 必须用try-finally块保证释放 |
线程饥饿 | 低优先级线程长期未执行 | 使用公平模式 |
死锁 | 多许可申请顺序不当 | 统一申请/释放顺序 |
响应中断 | 阻塞线程无法响应中断 | 使用acquireInterruptibly() |
2. 调试技巧
// 1. 打印信号量状态
System.out.println("可用许可: " + semaphore.availablePermits());
System.out.println("等待线程: " + semaphore.getQueueLength());// 2. 使用JMX监控
ManagementFactory.getPlatformMBeanServer().registerMBean(semaphore, new ObjectName("java.util.concurrent:type=Semaphore"));
八、与其他模式的组合应用
1. 信号量+线程池(流量整形)
ExecutorService executor = Executors.newCachedThreadPool();
Semaphore limiter = new Semaphore(20); // 最大20并发void submitTask(Runnable task) {limiter.acquire();executor.execute(() -> {try {task.run();} finally {limiter.release();}});
}
2. 信号量+CountDownLatch(阶段控制)
Semaphore semaphore = new Semaphore(5);
CountDownLatch latch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {new Thread(() -> {semaphore.acquire();try {// 阶段1:受限资源操作doPhase1Work();latch.countDown();// 阶段2:等待其他线程完成latch.await();doPhase2Work();} finally {semaphore.release();}}).start();
}
九、性能优化指南
1. 许可数计算公式
最大许可数 = (目标TPS × 平均耗时(秒)) / (1 - 冗余系数)
示例:
- 目标TPS=1000,平均耗时=0.1s,冗余系数=0.3
- 许可数 = (1000×0.1)/(1-0.3) ≈ 143
2. 不同场景下的参数建议
场景 | 许可数设置建议 | 公平性选择 |
数据库连接池 | 物理连接数的1.2倍 | 非公平 |
API限流 | 根据SLAB配额设置 | 公平 |
文件IO控制 | CPU核心数×2 | 非公平 |
十、扩展变体实现
1. 可动态调整的信号量
class DynamicSemaphore {private final ReentrantLock lock = new ReentrantLock();private Semaphore semaphore;public DynamicSemaphore(int permits) {this.semaphore = new Semaphore(permits);}public void addPermits(int delta) {lock.lock();try {if (delta > 0) {semaphore.release(delta);} else {int reduction = -delta;semaphore.acquire(reduction); // 减少可用许可}} finally {lock.unlock();}}
}
2. 超时自动释放信号量
class AutoReleaseSemaphore {private final Semaphore semaphore;private final ScheduledExecutorService scheduler;public AutoReleaseSemaphore(int permits) {this.semaphore = new Semaphore(permits);this.scheduler = Executors.newSingleThreadScheduledExecutor();}public void acquireWithTimeout(long timeout, TimeUnit unit) throws InterruptedException {semaphore.acquire();scheduler.schedule(() -> {semaphore.release();System.out.println("自动释放许可");}, timeout, unit);}
}
十一、行业应用案例
1. Kafka的吞吐控制
Kafka Producer使用Semaphore实现:
- 未确认请求数限制(max.in.flight.requests.per.connection)
- 内存缓冲区阻塞控制(buffer.memory)
2. Tomcat连接器配置
<!-- 在server.xml中配置信号量式连接限制 -->
<Connector executor="threadPool"maxConnections="10000" <!-- 信号量控制 -->acceptCount="100" <!-- 等待队列 -->
/>
相关文章:

并发设计模式实战系列(17):信号量(Semaphore)
🌟 大家好,我是摘星! 🌟 今天为大家带来的是并发设计模式实战系列,第十七章信号量(Semaphore),废话不多说直接开始~ 目录 一、核心原理深度拆解 1. 信号量本质模型 2. 并发控制…...

RAGMCP基本原理说明和相关问题解惑
一、RAG架构原理和局限性 1.1 概念解释 RAG(Retrieval-Augmented Generation):检索增强生成,让大模型接受外部输入后,总结输出 向量数据库:向量数据通常是高维空间中的点,代表复杂的数据结构…...

Java学习手册:服务注册与发现
一、服务注册与发现的概念 在微服务架构中,服务注册与发现是核心功能之一。由于微服务架构中服务实例的数量和位置是动态变化的,服务注册与发现机制允许服务实例在启动时自动注册到注册中心,并在停止时自动注销。其他服务可以通过查询注册中…...
双向Transformer:BERT(Bidirectional Encoder Representations from Transformers)
基于Transformer架构,通过双向上下文建模训练,提高完成任务的性能。 一 BERT的核心理念 1.1双向上下文建模依赖 之前讲的双向递归是用两个RNN进行,而BERT是通过Transformer的自注意力机制同时捕捉上下文信息。 1.1.1掩码语言模型…...

EdgeOne Pages MCP 入门教程
什么是MCP? MCP (Model Context Protocol) 是一个开放协议,允许 AI 模型安全地与本地和远程资源进行交互。通过在支持 MCP 的客户端(如 Cline、Cursor、Claude 等)上进行统一配置,可以让 AI 访问更多资源并使用更多工…...

Maven 公司内部私服中央仓库搭建 局域网仓库 资源共享 依赖包构建共享
介绍 公司内部私服搭建通常是为了更好地管理公司内部的依赖包和构建过程,避免直接使用外部 Maven 中央仓库。通过搭建私服,团队能够控制依赖的版本、提高构建速度并增强安全性。公司开发的一些公共工具库更换的提供给内部使用。 私服是一种特殊的远程仓…...

1688代采系统:技术架构与应用实践
在电商领域,1688 作为国内领先的 B2B 电商平台,拥有海量的商品信息。这些数据对于企业采购决策、市场分析、价格监控和供应链管理具有重要价值。本文将详细介绍如何使用 Python 爬虫技术,通过 1688 的商品详情接口(item_search 和…...

一种混沌驱动的后门攻击检测指标
摘要 人工智能(AI)模型在各个领域的进步和应用已经改变了我们与技术互动的方式。然而,必须认识到,虽然人工智能模型带来了显著的进步,但它们也存在固有的挑战,例如容易受到对抗性攻击。目前的工作提出了一…...

【2025最新】为什么用ElasticSearch?和传统数据库MySQL与什么区别?
Elasticsearch 深度解析:从原理到实践 一、为什么选择 Elasticsearch? 数据模型 Elasticsearch 是基于文档的搜索引擎,它使用 JSON 文档来存储数据。在 Elasticsearch 中,相关的数据通常存储在同一个文档中,而不是分散…...

c++的模板和泛型编程
c的模板和泛型编程 泛型编程函数模板函数模板和模板函数函数模板的原理函数模板的隐式、显式实例化模板参数的匹配原则 类模板类模板的实例化模板的使用案例用函数模板运行不同的模板类用函数模板运行不同的STL容器 模板的缺省参数非类型模板参数模板的特化函数模板的特化类模板…...

Java从入门到精通 - 数组
数组 此笔记参考黑马教程,仅学习使用,如有侵权,联系必删 文章目录 数组1. 认识数组2. 数组的定义和访问2.1 静态初始化数组2.1.1 数组的访问2.1.1 定义代码实现总结 2.1.2 数组的遍历2.1.2.1 定义代码演示总结 案例代码实现 2.2 动态初始化…...

MySql事务索引
索引 1.使用 创建主键约束(PRIMARY KEY)、唯一约束(UNIQUE)、外键约束(FOREIGN KEY)时,会自动创建 对应列的索引。 2.创建索引(普通索引) 事务:要么全部…...

八股文-js篇
八股文-js篇 1. 延迟执行js的方式2. js的数据类型3. null 和 undefined的区别4. 和 的区别5. js微任务和宏任务6. js作用域7. js对象9. JS作用域this指向原型8. js判断数组9. slice作用、splice是否会改变原数组10. js数组去重11. 找出数组最大值12. 给字符串新增方法实现功能…...
DeepSeek:开启教育测评智能化新时代
目录 一、引言二、DeepSeek 技术概述2.1 DeepSeek 的发展历程与特点2.2 工作原理与技术架构 三、测评试题智能生成3.1 生成原理与技术实现3.2 生成试题的类型与应用场景3.3 优势与面临的挑战 四、学生学习评价报告4.1 评价指标体系与数据来源4.2 DeepSeek 生成评价报告的流程与…...

【2025五一数学建模竞赛B题】 矿山数据处理问题|建模过程+完整代码论文全解全析
你是否在寻找数学建模比赛的突破点?数学建模进阶思路! 作为经验丰富的美赛O奖、国赛国一的数学建模团队,我们将为你带来本次数学建模竞赛的全面解析。这个解决方案包不仅包括完整的代码实现,还有详尽的建模过程和解析,…...

智能制造环形柔性生产线实训系统JG-RR03型模块式环形柔性自动生产线实训系统
智能制造环形柔性生产线实训系统JG-RR03型模块式环形柔性自动生产线实训系统 一、产品概述 (一)组成 柔性系统须有五个分系统构成即:数字化设计分系统、模拟加工制造分系统、检测装配分系统、生产物分流系统和信息管理分系统。它应包含供料检测单元,操作…...

1.2.2.1.4 数据安全发展技术发展历程:高级公钥加密方案——同态加密
引言 在密码学领域,有一种技术被图灵奖得主、著名密码学家Oded Goldreich誉为"密码学圣杯",那就是全同态加密(Fully Homomorphic Encryption)。今天我们就来聊聊这个神秘而强大的加密方案是如何从1978年的概念提出&…...
Java大师成长计划之第18天:Java Memory Model与Volatile关键字
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 在Java多线程编程中,线程…...

Lua再学习
因为实习的项目用到了Lua,所以再来深入学习一下 函数 函数的的多返回值 Lua中的函数可以实现多返回值,实现方法是再return后列出要返回的值的列表,返回值也可以通过变量接收到,变量不够也不会影响接收对应位置的返回值 Lua中传…...

GitLab搭建与使用(SSH和Docker)两种方式
前言 目前公共的代码仓库有很多,比如:git、gitee等等仓库但是我们在公司中,还是要搭建属于本公司自己的一个代码仓库,原因有如下几点 代码私密性,我们公司开发的代码保密性肯定一级重要,那么我们放到公网上,…...

Linux数据库篇、第零章_MySQL30周年庆典活动
MySQL考试报名网站 Oracle Training and Certification | Oracle 中国 活动时间 2025年 MySQL的30周年庆典将于2025年举行。MySQL于1995年首次发布,因此其30周年纪念日是2025年。为了庆祝这一里程碑,MySQL将提供免费的课程和认证考试,活动…...

Windows ABBYY FineReader 16 Corporate 文档转换、PDF编辑和文档比较
作为一名合格的工人,日常工作肯定离不开PDF文件,所以今天给大家找来了一款全新的PDF处理工具,保证能给你带来不一样的体验。 软件介绍 这是一个全能型的PDF处理器,集优秀的文档转换、PDF编辑和文档比较等功能于一身,…...
设计模式简述(十九)桥梁模式
桥梁模式 描述基本组件使用 描述 桥梁模式是一种相对简单的模式,通常以组合替代继承的方式实现。 从设计原则来讲,可以说是单一职责的一种体现。 将原本在一个类中的功能,按更细的粒度拆分到不同的类中,然后各自独立发展。 基本…...

【每日一题 | 2025年5.5 ~ 5.11】搜索相关题
个人主页:Guiat 归属专栏:每日一题 文章目录 1. 【5.5】P3717 [AHOI2017初中组] cover2. 【5.6】P1897 电梯里的尴尬3. 【5.7】P2689 东南西北4. 【5.8】P1145 约瑟夫5. 【5.9】P1088 [NOIP 2004 普及组] 火星人6. 【5.10】P1164 小A点菜7. 【5.11】P101…...
C语言速成之08循环语句全解析:从基础用法到高效实践
C语言循环语句全解析:从基础用法到高效实践 大家好,我是Feri,12年开发经验的程序员。循环语句是程序实现重复逻辑的核心工具,掌握while、do-while、for的特性与适用场景,能让代码更简洁高效。本文结合实战案例…...

多模态大语言模型arxiv论文略读(六十九)
Prompt-Aware Adapter: Towards Learning Adaptive Visual Tokens for Multimodal Large Language Models ➡️ 论文标题:Prompt-Aware Adapter: Towards Learning Adaptive Visual Tokens for Multimodal Large Language Models ➡️ 论文作者:Yue Zha…...
云计算-容器云-部署CICD-jenkins连接gitlab
安装 Jenkins 将Jenkins部署到default命名空间下。要求完成离线插件的安装,设置Jenkins的登录信息和授权策略。 上传BlueOcean.tar.gz包 [root@k8s-master-node1 ~]#tar -zxvf BlueOcean.tar.gz [root@k8s-master-node1 ~]#cd BlueOcean/images/ vim /etc/docker/daemon.json…...

精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
前引:在C语言长达三十余年的演进历程中,每一次标准更新都在试图平衡性能与抽象、控制与安全之间的微妙关系。从C11引入的"现代C"范式开始,开发者得以在保留底层控制能力的同时,借助语言特性大幅提升代码的可维护性与安全…...

【HarmonyOS 5】鸿蒙中常见的标题栏布局方案
【HarmonyOS 5】鸿蒙中常见的标题栏布局方案 一、问题背景: 鸿蒙中常见的标题栏:矩形区域,左边是返回按钮,右边是问号帮助按钮,中间是标题文字。 那有几种布局方式,分别怎么布局呢?常见的思维…...
Docker 部署 - Crawl4AI 文档 (v0.5.x)
Docker 部署 - Crawl4AI 文档 (v0.5.x) 快速入门 🚀 拉取并运行基础版本: # 不带安全性的基本运行 docker pull unclecode/crawl4ai:basic docker run -p 11235:11235 unclecode/crawl4ai:basic# 带有 API 安全性启用的运行 docker run -p 11235:1123…...