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

发布于 2024-12-06 19:06:22 字数 92 浏览 1 评论 0原文

我想用Java实现一个循环计数器。 每个请求的计数器应该(自动)递增,并且在达到上限时应该翻转到 0。

实现此目的的最佳方法是什么?是否有任何现有的实现?

I want to implement a circular counter in Java.
The counter on each request should increment (atomically) and on reaching an upper limit should roll over to 0.

What would be the best way to implement this and are there any existing implementations?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

守护在此方 2024-12-13 19:06:23

使用 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));
    }

}

With 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));
    }

}
俯瞰星空 2024-12-13 19:06:23

如果您担心使用 CAS 或同步的争用,那么您可以考虑更复杂的东西,例如建议的 JSR 166e LongAdder (来源javadoc )。

这是一个简单的计数器,多线程访问争用很少。您可以将其包装起来以公开(当前值 mod 最大值)。也就是说,根本不存储包装的值。

If you're that worried about contention using either CAS or synchronized then you could consider something more sophisticated like the proposed JSR 166e LongAdder (source, javadoc).

That's a straightforward counter with low contention on multithreaded access. You could wrap that to expose (current value mod max value). That is, don't store the wrapped value at all.

白首有我共你 2024-12-13 19:06:23

如果您使用模数运算符,则只需递增并返回模数即可。不幸的是,模数运算符很昂贵,因此我鼓励使用性能很重要的其他解决方案。

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 情况。

If you use the modulus operator, you could just increment and return the modulus. Unfortunately the modulus operator is expensive, so I encourage other solutions where performance is important.

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;

    }
}

You would have to solve the Long.MAX_VALUE case as well.

蓝戈者 2024-12-13 19:06:23

我个人认为 AtomicInteger 解决方案有点难看,因为它引入了竞争条件,这意味着您的更新尝试可能“失败”并且必须重复(通过在 while 循环内迭代),从而缩短更新时间与在关键部分内执行整个操作相比,确定性较差。

编写自己的计数器非常简单,我推荐这种方法。从面向对象的角度来看,它也更好,因为它只公开允许您执行的操作。

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 周期。

I personally think the AtomicInteger solution is a little ugly as it introduces a race-condition which means your update attempt could "fail" and have to be repeated (by iterating within the while loop) making the update time less deterministic than performing the entire operation within a critical section.

Writing your own counter is so trivial I'd recommend that approach. It's nicer from an OO-perspective too as it only exposes the operations you're allowed to perform.

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;
  }
}

EDIT

The other problem I perceive with the while loop solution is that given a large number of threads attempting to update the counter you could end up with a situation where you have several live threads spinning and attempting to update the counter. Given that only 1 thread would succeed, all other threads would fail causing them to iterate and waste CPU cycles.

醉酒的小男人 2024-12-13 19:06:23

您可以使用 java.lang. util.concurrent.atomic.AtomicInteger 类以原子方式递增。 至于设置上限并回滚到0,您需要在外部执行此操作...也许将所有这些封装在您自己的包装类中。

实际上,它看来您可以使用 compareAndSet 检查上限,然后翻转到 0

You can use the java.util.concurrent.atomic.AtomicInteger class to increment atomically. As for setting an upper bound and rolling back to 0, you'll need to do that externally...perhaps encapsulating all this within your own wrapper class.

Actually, it appears that you can use compareAndSet to check the upper bound, and then roll over to 0.

深者入戏 2024-12-13 19:06:23

我必须为自定义 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));
  }
}

I have to create a similar circular ticker for a custom Akka Routing logic which had to be different than the default ones to avoid network overhead, since my logic is just to pick the next routee.

Note: Copied from the suggested Java 8 implementation:

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));
  }
}
默嘫て 2024-12-13 19:06:23

对于由多个线程并行递增的高强度循环计数器,我建议使用 LongAdder(从 java 8 开始,请参阅 Striped64.java 中的核心思想),因为它是与 AtomicLong 相比更具可扩展性。很容易将其适应上述解决方案。

假设get操作在LongAdder中不那么频繁。调用 counter.get 时,对其应用“counter.get % max_number”。是的,模运算很昂贵,但对于这种用例来说并不常见,这应该分摊总性能成本。

但请记住,get 操作是非阻塞的,也不是原子的。

For highly-intensive circular counter incremented by multiple threads in parallel, I would recommend using LongAdder (since java 8, see the core idea inside Striped64.java), because it is more scalable compared to AtomicLong. It is easy to adapt it to the above solutions.

It is assumed that get operation is not so frequent in LongAdder. When calling counter.get, apply to it 'counter.get % max_number'. Yes, modulo-operation is expensive, but it is infrequent for this use-case, which should amortize the total performance cost.

Remember though, that get operation is non-blocking, nor atomic.

可爱咩 2024-12-13 19:06:22

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;
    }

}

It is easy to implement such a counter atop 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;
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文