【发布时间】:2011-11-26 05:03:41
【问题描述】:
我想在 Java 中实现一个循环计数器。 每个请求的计数器应该增加(原子地),达到上限时应该翻转到 0。
实现这一点的最佳方法是什么?是否有任何现有的实现?
【问题讨论】:
标签: java concurrency counter atomic
我想在 Java 中实现一个循环计数器。 每个请求的计数器应该增加(原子地),达到上限时应该翻转到 0。
实现这一点的最佳方法是什么?是否有任何现有的实现?
【问题讨论】:
标签: java concurrency counter atomic
在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;
}
}
【讨论】:
this.ai.get() 和} while (!this.ai.compareAndSet(curVal, newal)); 之间失去优先级,这是否真的是原子的
使用 Java 8
public class CyclicCounter {
private final int maxVal;
private final AtomicInteger counter = new AtomicInteger(0);
public CyclicCounter(int maxVal) {
this.maxVal = maxVal;
}
public long incrementAndGet() {
return counter.accumulateAndGet(1, (index, inc) -> (++index >= maxVal ? 0 : index));
}
}
【讨论】:
如果您使用模数运算符,您可以只增加并返回模数。不幸的是,模运算符很昂贵,所以我鼓励其他对性能很重要的解决方案。
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 的情况。
【讨论】:
counter超过long时你会怎么做?
我个人认为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 循环解决方案的另一个问题是,如果有大量线程尝试更新计数器,您最终可能会遇到多个活动线程旋转并尝试更新计数器的情况。假设只有 1 个线程会成功,所有其他线程都会失败,从而导致它们迭代并浪费 CPU 周期。
【讨论】:
您可以使用java.util.concurrent.atomic.AtomicInteger 类以原子方式递增。 至于设置上限并回滚到0,您需要在外部进行...也许将所有这些都封装在您自己的包装类中。
其实貌似可以用compareAndSet查看上限,然后翻车到0。
【讨论】:
我必须为自定义 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));
}
}
【讨论】:
对于由多个线程并行递增的高强度循环计数器,我建议使用LongAdder(从java 8开始,请参阅Striped64.java中的核心思想),因为它与AtomicLong相比更具可扩展性。很容易适应上述解决方案。
假设get操作在LongAdder中没有那么频繁。当调用counter.get 时,应用它'counter.get % max_number'。是的,模运算很昂贵,但对于这个用例来说并不常见,它应该摊销总性能成本。
但请记住,get 操作是非阻塞的,也不是原子的。
【讨论】:
AtomicInteger 如何从 Java 7 更改为 8,基本上 get 和 add 或 add 和 get 是在程序集级别完成的 -@987654331 @- 在这里解释:ashkrit.blogspot.com/2014/02/… LongAdder 是错误的工具,因为每次递增时都需要每个总和。
sun.misc.Unsafe 累积方法仍然调用compareAndSwapInt/Long 方法。也许这些 CAS 方法实际上并没有调用 CAS 处理器指令,但这些是native 方法,所以我们如何确定所有平台;毕竟 XADD 是特定于平台的指令,不是吗?