我写了一个简单的速率限制器来限制远程服务的使用:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
class SimpleRateLimiter {
private Semaphore semaphore;
private int maxPermits;
private TimeUnit timePeriod;
private ScheduledExecutorService scheduler;
public static SimpleRateLimiter create(int permits, TimeUnit timePeriod) {
SimpleRateLimiter limiter = new SimpleRateLimiter(permits, timePeriod);
limiter.schedulePermitReplenishment();
return limiter;
}
private SimpleRateLimiter(int permits, TimeUnit timePeriod) {
this.semaphore = new Semaphore(permits);
this.maxPermits = permits;
this.timePeriod = timePeriod;
}
public boolean tryAcquire() {
return semaphore.tryAcquire();
}
public void blockAcquire() throws InterruptedException {
semaphore.acquire();
}
public void stop() {
scheduler.shutdownNow();
semaphore.drainPermits();
}
public int getPermitCount() {
return semaphore.availablePermits();
}
public void schedulePermitReplenishment() {
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleWithFixedDelay(() -> {
semaphore.release(maxPermits - semaphore.availablePermits());
}, 0, 1, timePeriod);
}
}要使用它,我有以下条件:
SimpleRateLimiter rateLimiter = SimpleRateLimiter.create(100, TimeUnit.SECONDS);
...
//In some thread loop:
if (rateLimiter.tryAcquire()) {
System.out.println("Permit left: " + rateLimiter.getPermitCount());
...
}一切都很好,直到有一天它停止工作。检查日志,我发现rateLimiter.getPermitCount()达到104,(我怀疑)使maxPermits - semaphore.availablePermits()变为负值并抛出异常,导致schedulePermitReplenishment()内部的单线程调度器停止工作。我的问题是,考虑到信号量只能在内部访问且只能由单个线程访问,在什么情况下getPermitCount()可以超过100?
谢谢
发布于 2021-09-14 04:33:17
你的代码有一些问题。但有一个问题是schedulePermitReplenishment方法中存在潜在的竞争条件。
如果并发调用此方法,例如,您将拥有9个可用的许可和10个最大许可,那么您可能会得到以下结果:
semaphore.release(10 - 9);
它与sempahore.release(1)相同。因此,如果有两个线程并发调用这个函数,那么最终会得到11个许可,而不是10个。
正如您已经指出的,下一次调用schedulePermitReplenishment时,您将以10-11=-1的许可结束,该许可将被释放,并且您将以异常(它确实被执行器吞噬)结束。
你为什么需要补充许可证?票丢了吗?因为这可能是可用票数大于最大票数的另一个来源。例如,如果取了一张票,你会调用replenish,而票最终会被退回,你最终也会得到太多的票。
我不太确定这段代码将如何使用,但您也可以在executor字段上进行数据竞争。也许在构造函数中创建executor而不是替换它会更好。
https://stackoverflow.com/questions/69171759
复制相似问题