信号量是可控并发闸门而非万能锁,需结合数据库锁保障原子性;初始许可应设为并发阈值而非业务上限;release必须兜底执行;公平性依场景权衡;优先选用轻量级实现。

信号量不是万能锁,而是可控的“并发闸门”——它不阻止线程进入,而是按设定额度放行。用对了,系统稳如磐石;用错了,许可泄露、超卖、阻塞雪崩接踵而至。关键不在会不会调用 acquire/release,而在策略是否匹配真实场景。
明确资源边界:初始许可数 ≠ 业务上限
很多人把信号量初始化为“库存数量”或“连接池大小”,却忽略了操作非原子性。比如秒杀场景中,商品库存100,若直接 new Semaphore(100),一旦多个线程在扣减库存前都通过 acquire(),后续数据库写入未加锁或未做CAS校验,就会出现超卖。
- 信号量只管“准入”,不管“执行结果”——它限制的是并发请求量,不是最终业务成功数
- 真正安全的库存控制需组合使用:信号量(限流入口)+ 数据库行锁或乐观锁(保证扣减原子性)
- 推荐做法:初始许可设为合理并发阈值(如系统能稳定处理的QPS峰值),而非业务数据本身
释放必须兜底:永远放在 finally 或 using 块里
许可泄露是生产环境最常见故障源。一次异常抛出导致 release() 被跳过,信号量计数卡死,后续所有线程在 acquire() 处无限等待。
- Java 中务必用 try-finally 包裹业务逻辑,确保 release() 无条件执行
- C# 中优先使用 SemaphoreSlim.WaitAsync() + await using 或 try/finally,避免同步阻塞影响吞吐
- 切忌在 if 分支里 conditional release,也不要在 return 前写 release —— 它必须是确定性动作
公平性要权衡:非公平提升吞吐,公平避免饥饿
默认非公平模式下,新线程可能“插队”抢到刚释放的许可,老等待线程一直被跳过;开启公平模式则严格 FIFO,但整体吞吐下降约15–20%。
- 高吞吐短任务(如API网关限流)选非公平,响应更快
- 长耗时关键任务(如订单生成、支付回调)建议公平,防止个别请求积压超时
- 注意:公平模式不能完全杜绝饥饿,只是大幅降低概率;极端负载下仍需配合超时机制(tryAcquire(timeout))
选型与性能:轻量级优先,跨进程再考虑内核级
多数服务内部资源协调无需跨进程,SemaphoreSlim(C#)或普通 Semaphore(Java)已足够。盲目用内核级信号量反而拖慢性能。
- C# 场景优先用 SemaphoreSlim:内存开销小、支持 async/await、单机高性能
- 仅当需跨进程同步(如多实例共享本地缓存锁)才用 Semaphore(基于Win32 Mutex/Event)
- Java 中无轻重之分,但要注意构造时 fair 参数显式指定,避免默认非公平引发不可预期调度


















