我想在Java中实现循环计数器。 每个请求的计数器应该(原子地)递增,并且在达到上限时应该翻转到0.
实现这一目标的最佳方式是什么?是否存在任何实现?
答案 0 :(得分:20)
在AtomicInteger
上面实施这样的计数器很容易:
public class CyclicCounter {
private final int maxVal;
private final AtomicInteger ai = new AtomicInteger(0);
public CyclicCounter(int maxVal) {
this.maxVal = maxVal;
}
public int cyclicallyIncrementAndGet() {
int curVal, newVal;
do {
curVal = this.ai.get();
newVal = (curVal + 1) % this.maxVal;
} while (!this.ai.compareAndSet(curVal, newVal));
return newVal;
}
}
答案 1 :(得分:7)
如果您担心使用CAS或synchronized
进行争用,那么您可以考虑像提议的JSR 166e LongAdder
(source,javadoc)那样更复杂的内容
这是一个简单的计数器,在多线程访问时争用率很低。您可以将其包装为公开(当前值mod最大值)。也就是说,根本不要存储包装的值。
答案 2 :(得分:6)
使用Java 8
public class CyclicCounter {
private final int maxVal;
private final AtomicInteger counter = new AtomicInteger(0);
public CyclicCounter(int maxVal) {
this.maxVal = maxVal;
}
return counter.accumulateAndGet(1, (index, inc) -> {
return ++index >= maxVal ? 0 : index;
});
}
答案 3 :(得分:2)
我个人认为AtomicInteger
解决方案有点难看,因为它引入了竞争条件,这意味着您的更新尝试可能“失败”并且必须重复(通过在while循环内迭代)来进行更新时间在临界区内执行整个操作的确定性较低。
编写自己的计数器是如此微不足道,我建议采用这种方法。从OO角度看它也更好,因为它只暴露了你允许执行的操作。
public class Counter {
private final int max;
private int count;
public Counter(int max) {
if (max < 1) { throw new IllegalArgumentException(); }
this.max = max;
}
public synchronized int getCount() {
return count;
}
public synchronized int increment() {
count = (count + 1) % max;
return count;
}
}
修改强>
我认为使用while循环解决方案的另一个问题是,如果有大量线程尝试更新计数器,您可能会遇到一些情况,即您有多个活动线程正在旋转并尝试更新计数器。鉴于只有一个线程会成功,所有其他线程都会失败,导致它们迭代并浪费CPU周期。
答案 4 :(得分:2)
如果使用模数运算符,则可以增加并返回模数。不幸的是,模数运算符很昂贵,所以我鼓励其他性能很重要的解决方案。
public class Count {
private final AtomicLong counter = new AtomicLong();
private static final long MAX_VALUE = 500;
public long getCount() {
return counter.get() % MAX_VALUE;
}
public long incrementAndGet(){
return counter.incrementAndGet() % MAX_VALUE;
}
}
您还必须解决Long.MAX_VALUE案例。
答案 5 :(得分:1)
您可以使用java.util.concurrent.atomic.AtomicInteger
类以原子方式递增。 至于设置上限并回滚到 0
,您需要在外部执行此操作...或者将所有这些封装在您自己的包装类中。
实际上,您似乎可以使用compareAndSet
检查上限,然后转到0
。
答案 6 :(得分:1)
我必须为自定义Akka路由逻辑创建一个类似的循环滚动条,它必须与默认路由逻辑不同,以避免网络开销,因为我的逻辑只是选择下一个路由器。
注意:从建议的Java 8实现中复制:
import akka.routing.Routee;
import akka.routing.RoutingLogic;
import scala.collection.immutable.IndexedSeq;
import java.util.concurrent.atomic.AtomicInteger;
public class CircularRoutingLogic implements RoutingLogic {
final AtomicInteger cycler = new AtomicInteger();
@Override
public Routee select(Object message, IndexedSeq<Routee> routees) {
final int size = routees.size();
return size == 0 ? null : routees.apply(cycler.getAndUpdate(index -> ++index < size ? index : 0));
}
}
答案 7 :(得分:0)
对于高度密集的循环计数器并行增加多个线程,我建议使用LongAdder
(因为java 8,请参阅Striped64.java
中的核心思想),因为与AtomicLong
相比,它更具可扩展性{1}}。很容易使其适应上述解决方案。
假设get
中的LongAdder
操作并不常见。致电counter.get
时,请向其申请'counter.get%max_number'。是的,模运算很昂贵,但这个用例并不常见,应该分摊总的性能成本。
请记住,get
操作是非阻塞的,也不是原子的。