semaphore本质是用aqs的volatile state字段表示可用许可数,初始化即设为permits值;acquire()通过cas原子减1,release()原子加1;state为0时线程入队等待,全程无锁依赖cas保证线程安全。

信号量(Semaphore)的计数原理,本质是用一个原子更新的整型状态值(state)来模拟“可用许可证数量”,所有并发安全操作都围绕这个值展开,不依赖锁本身做互斥,而是靠底层CAS指令保障一致性。
计数器怎么来的?——state 就是 permits
在 Java 的 Semaphore 实现中,内部同步器(继承自 AQS)将 state 字段直接映射为当前剩余许可数。初始化时传入的 N,就是 state 的初始值。比如 new Semaphore(5),AQS 的 state 就设为 5。
- 每次
acquire()成功,state 原子减 1(通过 CAS 循环尝试) - 每次
release()执行,state 原子加 1(同样 CAS 保证) - state == 0 时,后续 acquire 无法立即成功,线程被挂起并加入 AQS 同步队列
为什么能保证线程安全?——全靠 CAS + volatile
state 是 volatile 修饰的 int,所有读写都具备可见性;而增减操作全部封装在 compareAndSetState() 调用中,即典型的无锁乐观策略:
- 没有锁竞争时,获取/释放许可几乎就是一次 CPU 指令,开销极低
- 多个线程同时 try-acquire:谁 CAS 成功谁拿到许可,失败者重试或排队
- 即使高并发下发生 CAS 失败,也不会丢失状态,机制天然防覆盖
计数不是“实时快照”,而是“许可流转记录”
availablePermits() 返回的是当前 state 值,但它反映的不是某一毫秒的瞬时快照,而是从初始化到此刻,所有 release 和 acquire 成功操作累积后的净结果。它不承诺强一致性读取,但对限流类场景已足够可靠。
- 例如:两个线程同时调用 acquire(),state 从 2 变成 0,中间不存在 “1.5” 这种中间态
- 计数永远是非负整数,不会溢出(acquire 不允许在 0 时继续减)
- 如果用
tryAcquire(2)请求 2 个许可,必须满足 state ≥ 2 才会成功,否则整个操作失败或阻塞
公平性如何影响计数行为?
公平模式下,即使 state > 0,新来的线程也可能让位于队列头的等待者;而非公平模式下,新线程可直接参与 CAS 竞争——计数逻辑不变,但许可分配顺序不同:
- 非公平:可能造成“插队”,刚 release 的许可未必给最早等待的线程
- 公平:严格 FIFO,等待队列头线程优先获得下一次可用许可,计数更新时机略滞后但语义更可预测











