所以这段代码:
int usedPermits = totalPermits - semaphore.availablePermits();
semaphore.release(usedPermits);
不是线程安全的,因为如果在两行之间另一个线程释放许可证,信号量的容量实际上将增加到其原始最大值之上。
这适用于我的情况,因为这段代码是1)单线程和2)唯一发布许可证的地方,这可能只是说明“全部释放”和“获取/释放”两个事实同一对象上的设计模式不兼容。
但是,我想询问是否存在具有不太精细的线程同步策略的首选模式。
答案 0 :(得分:3)
正如其他答案中所解释的那样,有一种解决方案可以使您的代码成为原子。但是,在一般情况下,无法保证它会解决问题,因为这些代码在所有情况下都不正确。
使用它们的活动释放许可证,在这种情况下代码是多余的:信号量将自然补充,或者它们不会,在这种情况下你将需要那段代码,它将是安全的,只要你不做任何奇怪的事情。
请注意,您声明您希望限制每个时间段的活动率(此处为一分钟),但您必须考虑到活动的持续时间可能超过一分钟。你可以在这里限制两件事:
如果您希望限制第一个,那么您将需要您的代码来重新填写许可证,并让活动保留其许可证。如果你想 要处理第二种情况,那么你必须强制活动在分别开始和终止时获取和释放他们的许可。
如果您害怕某些活动滥用信号量,请禁止在活动代码中使用它。实际上,速率限制与活动语义完全正交,并且最好将该功能与活动主代码分开。因此,您应该使用代码来包装任何计划的活动来处理信号量:
class RateLimitedRunnable implements Runnable {
Runnable runnable;
RateLimitedRunnable(Runnable r) { runnable = r; }
void Run() {
semaphore.acquire();
runnable.run();
semaphore.release(); // remove if only limiting starts
}
}
上面的示例(未经测试)代码描述了使用信号量远离实际活动的可能处理,从而消除了任何可能的误用。 如果内部活动需要访问信号量,那么它应该只是检索它的当前状态,并且肯定可以设计一个ad-hoc接口来提供有限的访问。
注意:我在这里使用“活动”一词作为线程或进程的意思,因为关于使用信号量的讨论比 Java 的上下文更通用。
答案 1 :(得分:0)
不是线程安全的,因为如果在两行之间另一个线程释放许可证,信号量的容量实际上将增加到其原始最大值以上。
这应该由synchronization
解决,同时强制每个semaphore.release()
都在synchronized
块中,例如
synchronize(lock) {
int usedPermits = totalPermits - semaphore.availablePermits();
semaphore.release(usedPermits);
}
但是,如果您希望确保 不超过totalPermits
的许可数量,这可能还不够,因为即使{{1}也是如此} 释放所有块,如果一个名为synchronized
的帖子在release
之后超过semaphore
。
你可以实现一个totalPermits
调用释放许可而不直接调用threads
的函数,例如。
semaphore.release()
答案 2 :(得分:0)
如果它甚至可行的话,将某些东西粘在Semaphore上有点奇怪。这也会导致突发性 - 如果你每秒限制到10k QPS,那么你将重置并立即获得10000个查询。为什么不使用现有的RateLimiter实现?:
至少可以看一下代码的灵感:
答案 3 :(得分:0)
为什么不扩展标准的Semaphore类并覆盖它的方法,但要使它们同步:
public class Semaphore extends java.util.concurrent.Semaphore {
public Semaphore(int permits)
{
super(permits);
}
public synchronized void releaseAll()
{
super.release(super.drainPermits());
}
}