如何在Java中实现并发循环自动收报机(计数器)?

时间:2011-09-27 16:22:44

标签: java concurrency counter atomicity

我想在Java中实现循环计数器。 每个请求的计数器应该(原子地)递增,并且在达到上限时应该翻转到0.

实现这一目标的最佳方式是什么?是否存在任何实现?

8 个答案:

答案 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 LongAddersourcejavadoc)那样更复杂的内容

这是一个简单的计数器,在多线程访问时争用率很低。您可以将其包装为公开(当前值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操作是非阻塞的,也不是原子的。