semaphore本质是“带等待队列的原子计数器”,所有并发控制基于aqs的state字段(可用许可数),通过cas增减;acquire/release需严格配对置于临界区入口与finally块中,否则导致许可泄漏。

Semaphore 的底层逻辑本质是一个“带等待队列的原子计数器”,它不锁资源本身,而是通过控制线程能否拿到“通行证”(permit)来间接限流。它的力量不在复杂,而在精准——所有并发控制都落在 AQS 的 state 字段 上,这个整型变量就是当前可用许可数。
许可证就是 state 值,增减全靠 CAS 原子操作
初始化时传入的 permits 数,直接设为 AQS 的初始 state 值。之后:
- acquire() 就是循环执行:读取当前 state → 计算 newState = state - 1 → 用 compareAndSetState(state, newState) 尝试更新;失败就重试,直到成功或被阻塞
- release() 同理:读取 state → newSate = state + 1 → CAS 更新;成功后还会唤醒 AQS 同步队列里的一个或多个等待线程
- 整个过程没有锁,纯靠硬件级 CAS 指令保证线程安全,开销极小
阻塞不是靠 Object.wait(),而是进 AQS 等待队列挂起
当 acquire 发现 state ≤ 0,线程不会自旋耗 CPU,而是:
- 被包装成 Node 节点,插入 AQS 的 CLH 同步队列尾部
- 调用 LockSupport.park() 主动挂起自己
- 只有当其他线程 release 并调用 unpark(),或被中断/超时时,才被唤醒重新竞争
公平性由队列策略决定,不是“先到先得”的错觉
公平模式(fair = true)下,acquire 会先检查队列里有没有前驱节点——如果有,说明别人在等,自己必须排队;非公平模式则直接尝试 CAS,抢到了就走,没抢到再排队。这导致:
- 公平模式吞吐低但无饥饿,适合对响应时间敏感的场景(如支付回调)
- 非公平模式吞吐高,但可能让长等待线程一直被新来的“插队”线程压制
限流效果来自 acquire/release 的配对约束,不是魔法
真正起限流作用的,是业务代码中 必须把 acquire 放在临界区入口、release 放在 finally 块里。例如:
semaphore.acquire();try {
// 执行数据库查询、HTTP 调用等耗资源操作
} finally {
semaphore.release();
}
一旦漏掉 release,许可就永久丢失,后续所有线程都会卡死——这不是 Semaphore 的缺陷,而是使用契约。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











