You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中Semaphore与调度器竞态条件致自定义限流器许可数超限问题

问题原因

导致Semaphore许可数超过设定上限,核心是许可补全逻辑的非原子性+多补全任务并发执行,具体触发场景如下:

  • schedulePermitReplenishment方法是public修饰,若外部代码不小心多次调用该方法,每次调用都会新建一个独立的单线程调度器,同时启动一个新的许可补全定时任务。多个补全任务并行执行时,就会出现超量发放许可的问题:
    举个例子,maxPermits=100,当前可用许可为90,两个补全任务同时执行:
    1. 任务A读取可用许可为90,计算得到需要补充10个许可
    2. 任务B同时读取可用许可为90,同样计算得到需要补充10个许可
    3. 任务A先执行release(10),可用许可变为100
    4. 任务B再执行release(10),可用许可直接变为110,超过上限
      Semaphore本身的设计没有对许可总数做上限校验,只要调用release方法就会增加许可数,因此多补全任务并发时必然会出现许可数超标的情况。

后续限流器失效的原因

当可用许可数超过maxPermits后,下一次补全任务计算maxPermits - semaphore.availablePermits()会得到负数,而Semaphore的release方法传入负数参数会直接抛出IllegalArgumentException。ScheduledExecutorService的定时任务如果抛出未捕获的异常,后续的调度会直接终止,不会再继续补全许可,最终许可被业务线程消耗完之后,限流器就完全失效了。

修复方案

// 1. 将schedulePermitReplenishment改为private,禁止外部调用
private void schedulePermitReplenishment() {
    scheduler = Executors.newScheduledThreadPool(1);
    scheduler.scheduleWithFixedDelay(() -> {
        // 2. 加try-catch避免异常导致调度终止
        try {
            int gap = maxPermits - semaphore.availablePermits();
            // 3. 仅当需要补充的许可数为正时才执行release
            if (gap > 0) {
                semaphore.release(gap);
            }
        } catch (Exception e) {
            // 可在此处添加异常日志打印
        }
    }, 0, 1, timePeriod);
}

如果需要更严谨的防护,可以额外添加一个原子状态变量,标记补全任务是否已经启动,避免重复初始化调度器。

内容的提问来源于stack exchange,提问作者vpork

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 15:00:01