semaphore通过acquire()和release()与runnable协同实现并发限流:初始化指定许可数,任务执行前获取、结束后释放许可,需用try-finally确保释放,避免许可泄漏。

在Java中,用Runnable配合Semaphore实现多线程并发限流,核心是控制同时执行的任务数量,避免资源过载。关键在于:信号量初始化时指定许可数(即最大并发数),每个任务执行前获取许可、执行完释放许可,从而天然形成“准入-退出”节流机制。
Semaphore如何与Runnable协同工作
Semaphore本身不参与线程调度,它只提供许可的申请与归还能力;Runnable定义任务逻辑;二者通过在run()方法中显式调用acquire()和release()绑定在一起。注意必须用try-finally确保释放,否则许可泄漏会导致后续任务永久阻塞。
- 初始化
Semaphore semaphore = new Semaphore(3);表示最多3个任务可并行 - 每个
Runnable实例在run()开头调用semaphore.acquire(); - 在
finally块中调用semaphore.release(); - 提交到线程池(如
Executors.newFixedThreadPool(10))后,实际并发由Semaphore控制,而非线程池大小
完整可运行示例代码
以下是一个模拟API调用限流的简单例子,限制最多2个请求同时执行:
import java.util.concurrent.*;
public class SemaphoreRateLimiter {
private static final Semaphore semaphore = new Semaphore(2); // 最大并发2
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i
运行时会观察到:任意时刻最多只有2个“started”输出,其余任务排队等待,印证了限流生效。
进阶要点与避坑提醒
真实场景中需关注超时控制、公平性、异常处理等细节:
- 使用
acquireUninterruptibly()或带超时的tryAcquire(long timeout, TimeUnit unit),避免线程被意外中断后无法获取许可 - 构造
Semaphore时传true启用公平模式(FIFO),防止某些任务长期饥饿;默认非公平,性能略高但可能造成不公平等待 - 不要在
run()里捕获InterruptedException却不恢复中断状态——应调用Thread.currentThread().interrupt() - 若任务本身已在线程池中执行,切勿重复创建新线程,否则会绕过Semaphore控制
对比其他限流方式的适用场景
Semaphore适合粗粒度、基于许可数的并发控制,比如数据库连接池、外部HTTP客户端并发数限制。它不关心QPS、时间窗口或用户维度,也不做平滑限流(如令牌桶)。若需更精细控制,可考虑:
– RateLimiter(Guava)用于QPS限流
– Resilience4j或Sentinel用于分布式/多维规则限流
– 自研滑动窗口计数器用于统计类限流
单纯本地并发数限制,Semaphore轻量、无依赖、语义清晰,是最直接的选择。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











