令牌桶算法用Semaphore模拟需配合定时任务周期性release许可来匀速加令牌,初始化设最大容量,acquire(1)消耗令牌,非公平模式+超时tryAcquire保障线程安全与响应性,预填充和监控可优化突发流量与调优。

令牌桶算法的核心思想是:系统以恒定速率向桶中添加令牌,请求需要先获取令牌才能执行,桶满则丢弃新令牌,无令牌则拒绝请求。用 Semaphore 模拟令牌桶的关键在于——它本身不支持“自动补充”,必须配合外部定时任务来周期性地释放许可(即“加令牌”),从而模拟平滑的填充行为。
用 Semaphore 表示令牌桶容量
Semaphore 的许可数直接对应桶中当前可用令牌数。初始化时设为最大容量(如 100),表示桶初始满载;每次请求尝试 acquire(1),成功即消耗一个令牌;失败则说明桶空,触发限流逻辑。
- 注意设置为非公平模式(默认),避免线程排队饥饿
- 不使用
acquireUninterruptibly,保留中断响应能力,便于服务优雅停机 - 建议封装成线程安全的工具类,避免多处直接操作
Semaphore
定时任务负责“匀速加令牌”
令牌桶强调“稳定速率”,例如每秒放 10 个令牌。可使用 ScheduledExecutorService 每 100ms 尝试释放 1 个许可(10 × 0.1 = 1),但需防止超发:只在未达最大容量时才 release。
- 通过
semaphore.availablePermits() 判断是否还能加令牌 - 用
semaphore.release(1)增加一个令牌,不是重置,而是累加 - 调度周期越小,填充越平滑,但调度开销略增;100ms 是较平衡的选择
处理突发与预热场景
纯定时填充在服务刚启动或低流量后突增时,桶中令牌少,容易误限流。可结合“最大突发量”设计:允许桶容量短暂超过基础值(如 +20%),或启动时预填充部分令牌。
- 预填充可在初始化后立即
release(n),比如填满一半 - 若需支持动态调整速率,需同步更新定时任务周期和容量判断逻辑
- 监控
availablePermits()和拒绝次数,有助于调优参数
注意边界与线程安全细节
Semaphore 本身线程安全,但定时任务 + acquire 组合仍需注意几个隐含问题:
- 定时任务可能因 GC 或调度延迟导致短时间补慢,属于正常现象,算法本身容忍小幅抖动
- 不要在
acquire阻塞时做耗时操作,应设超时(如tryAcquire(1, 100, TimeUnit.MILLISECONDS)) - 关闭服务时,应主动
shutdown定时器,并清空或记录剩余令牌状态(如需持久化)










