说说 Semaphore 原理
Semaphore 直译为信号。实际上 Semaphore 可以看做是一个信号的集合。不同的线程能够从 Semaphore 中获取若干个信号量。当 Semaphore 对象持有的信号量不足时,尝试从 Semaphore 中获取信号的线程将会阻塞。直到其他线程将信号量释放以后,阻塞的线程会被唤醒,重新尝试获取信号量。
信号量模型(P/V 操作)由 Dijkstra 提出,是操作系统课的经典内容;Java 的 Semaphore 把它落成了 AQS 共享模式的一个应用。本文讲清三件事:AQS 层如何实现许可计数、公平与非公平的差异、以及它在限流场景的正确用法与常见误用。
模型:许可(permits)池
Semaphore 维护一组许可:
acquire():取一个许可,无许可则阻塞(P 操作)release():还一个许可,可能唤醒等待者(V 操作)- 构造参数 permits 决定同时允许多少个线程通过:permits=1 时退化为互斥锁;permits=N 时允许 N 个并发
Semaphore sem = new Semaphore(3); // 最多 3 个线程同时访问资源 |
AQS 实现:共享模式的状态计数
Semaphore 内部 Sync 继承 AQS,state 即剩余许可数:
- acquire →
acquireSharedInterruptibly(1):CAS 把 state 减 1;减后为负(不足)则入队 park 阻塞 - release →
releaseShared(1):CAS 把 state 加 1,并唤醒等待队列头部线程(共享模式传播唤醒) - 支持一次获取多个许可:
acquire(n),state 减 n
与 CountDownLatch(state 只减到 0 开门)不同,Semaphore 的 state 可增可减、循环使用,这是”资源池”语义与”门闩”语义在 AQS 上的分野。
公平与非公平
- 非公平(默认):新到线程直接 CAS 抢许可,可能”插队”成功,吞吐高但等待者可能饥饿
- 公平:
new Semaphore(n, true),acquire 前先检查等待队列是否有排队者(hasQueuedPredecessors),有则乖乖排队,FIFO 保证不饥饿
限流场景若要求”先到先得、不饿死”,用公平模式;追求极限吞吐且容忍偶发长等待,用非公平。
限流场景与误用
正确用法:
- 保护有限资源:数据库连接数、下游 API 并发配额、硬件设备通道——permits 设为资源容量
- 降级限流:接口入口用 Semaphore 限制并发执行数,超限请求快速失败或排队等待(配合 tryAcquire 超时)
- 批量任务控速:爬虫/导出任务控制同时进行的下载数
常见误用:
- release 与 acquire 不成对:异常路径漏 release,许可逐渐耗尽直至全阻塞;必须 finally
- 用 Semaphore 做业务计数:它是并发控制原语,不是计数器(计数用 LongAdder/CHM)
- permits 设过大失去保护意义:限流值应来自下游容量压测,而非拍脑袋
- acquire 后、业务代码前抛异常:确保 acquire 成功才进入 try 块(acquire 放 try 外或捕获中断正确处理)
与同类原语定位
- ReentrantLock:互斥(1 个许可且绑定持有者,可重入)
- Semaphore(1):互斥但不绑定持有者(任何线程可 release,可用于”信号”式协作)
- Semaphore(N):并发限流
- RateLimiter(Guava):速率限流(QPS 维度),与 Semaphore 的并发数限流互补
Semaphore 的原理一句话:AQS 共享模式下”可增可减的 state 计数 + 不足即排队”。它的价值不在复杂,而在把”资源容量”这个业务约束直接表达为并发原语——用对了,它是限流与资源保护最轻量的正解。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 非鱼小站!
评论
WalineDisqus











