semaphore性能取决于aqs同步队列使用方式:公平模式避免饥饿但吞吐低,非公平模式吞吐高但可能饥饿;推荐批量acquire/release、优先tryacquire超时获取、按真实负载设初始许可数。

Semaphore 的底层同步机制依赖 AQS(AbstractQueuedSynchronizer),其性能表现直接受同步队列行为与使用方式影响。关键不在“用不用”,而在“怎么用”——尤其在高并发、低延迟场景下,队列策略和许可操作方式会显著改变吞吐量与响应一致性。
同步队列本质是 FIFO 队列,但公平性决定调度逻辑
无论公平还是非公平模式,Semaphore 都基于 AQS 的 CLH 同步队列管理等待线程。区别在于获取许可时是否检查前置节点:
- 公平模式:每次
acquire()前调用hasQueuedPredecessors(),确保只有队首线程能尝试获取;新线程一律入队尾,避免插队 - 非公平模式:跳过前置检查,直接用 CAS 尝试扣减
state(即剩余许可数);成功则立即执行,失败才入队 - 公平模式虽避免饥饿,但频繁唤醒+上下文切换易拉低吞吐;非公平模式吞吐更高,但长等待线程可能被持续“跳过”
许可操作粒度影响锁竞争强度
单次 acquire() 默认争抢 1 个许可,但在资源批量处理场景中,小粒度操作会放大 AQS 的 CAS 竞争和队列维护开销:
- 推荐在业务允许前提下,使用
acquire(int permits)批量获取,例如一次申请 5 个许可而非循环调用 5 次 - 对应地,
release(int permits)也应匹配批量释放,减少 state 更新次数和唤醒频次 - 注意:批量操作不改变总许可数守恒,但显著降低同步路径执行频率,实测在 QPS > 5k 场景下可提升 15%~30% 吞吐
避免无意义的阻塞等待
不是所有场景都需要阻塞式获取许可。若业务能容忍“当前不可用即跳过”,应优先选用非阻塞方式:
-
tryAcquire()或tryAcquire(long timeout, TimeUnit unit)可设超时,防止线程长期挂起 - 对时效敏感任务(如实时风控、消息投递),建议结合
tryAcquire(1, 100, TimeUnit.MILLISECONDS)控制最大等待时间 - 若获取失败,可降级为异步重试、缓存兜底或快速失败,而非让线程空等消耗调度资源
初始化与运行期许可数需匹配真实负载
初始 permits 设置偏小会导致大量线程排队;过大则失去限流意义,甚至掩盖资源瓶颈:
- 根据下游资源容量设定(如数据库连接池大小、HTTP 客户端最大连接数),而非拍脑袋估算
- 动态场景可配合监控(如平均排队时长、拒绝率)做运行期调整,但注意
release()和acquire()不改变初始值,仅调节可用数 - 必要时用
drainPermits()快速清空许可(用于熔断后快速归零),或availablePermits()实时观测剩余量辅助决策
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











