semaphore 是配额管理工具而非锁,核心是控制并发数而非资源归属;许可数需依资源瓶颈设定,获取释放须成对且进 finally,支持动态扩缩容与公平性选择。

Semaphore 不是用来“锁住”资源的,而是用来“配额管理”的——它不关心谁在用、用了多久,只管当前有多少个线程正在用,总数不能超限。调节并发访问量,关键不在设多大值,而在怎么用、何时调、如何兜底。
许可数设置:不是拍脑袋,得看资源瓶颈
初始许可数不能直接等于库存或连接池大小,必须结合实际资源承载力评估:
- 数据库连接池:若最大连接数是 20,但平均查询耗时 200ms,QPS 峰值约 100,则 许可数建议设为 50~80,留出缓冲避免连接争抢和超时雪崩
- 秒杀接口:商品库存 1000,但单次扣减含 Redis+DB 两阶段操作,平均耗时 150ms → 理论最大吞吐 ≈ 1000 ÷ 0.15s ≈ 6667 QPS,但真实瓶颈常在 DB 写入或缓存穿透 → 实际许可数宜从 200 起压测,逐步上探至 500 左右,观察错误率与响应 P99
- 避免“许可数 = 库存数”陷阱:库存是业务约束,信号量是并发控制,二者语义不同;超卖风险来自非原子操作,信号量本身不保证事务一致性,仅作第一道流量闸门
获取与释放:必须成对,且必须进 finally
许可泄露是生产环境最常见故障源,本质是 acquire 和 release 失配:
- 永远在 try-finally 中释放:哪怕业务逻辑抛出 RuntimeException 或 Error,也要确保 release 执行
- 禁止在 if 分支里 release:如“成功才释放”,失败路径遗漏会导致许可永久丢失
- 慎用批量 acquire:semaphore.acquire(5) 适合固定资源单元(如一次处理 5 条消息),但若中途某条失败,需手动补偿释放,否则易引发许可错配
动态调节:运行时适配流量变化
静态许可数扛不住突增流量或低谷闲置,需支持热调整:
- 扩容:调用 semaphore.release(n) 增加可用许可(注意:release 不校验当前持有者,相当于“白送”许可,可用于预热或弹性扩容)
- 缩容:用 semaphore.reducePermits(n) 主动回收许可,适用于降级、维护或突发限流
- 监控联动:每秒采集 availablePermits() + 阻塞线程数(可通过 ThreadMXBean 获取),当可用许可持续 50,触发告警并自动 reducePermits(20)
公平性选择:按场景选 FIFO 还是抢跑
公平模式(构造时传 true)保障请求顺序,但带来额外队列开销;非公平模式性能略优,但可能造成“饥饿”:
- 秒杀/抢购类场景:选 非公平 —— 允许后到线程插队成功,提升整体吞吐,避免长尾请求卡死
- 后台任务调度/资源池管理:选 公平 —— 防止某些任务长期得不到执行,保障 SLA 可预期
- 实测数据:在 1000 并发下,非公平模式平均 acquire 耗时比公平低 15%~20%,但尾部延迟波动更大











