如何在Java中实现并发循环收报机(计数器)?
我想用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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
使用 Java 8
With Java 8
如果您担心使用 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 166eLongAdder
(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.
如果您使用模数运算符,则只需递增并返回模数即可。不幸的是,模数运算符很昂贵,因此我鼓励使用性能很重要的其他解决方案。
您还必须解决 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.
You would have to solve the Long.MAX_VALUE case as well.
我个人认为 AtomicInteger 解决方案有点难看,因为它引入了竞争条件,这意味着您的更新尝试可能“失败”并且必须重复(通过在 while 循环内迭代),从而缩短更新时间与在关键部分内执行整个操作相比,确定性较差。
编写自己的计数器非常简单,我推荐这种方法。从面向对象的角度来看,它也更好,因为它只公开允许您执行的操作。
编辑
我认为 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.
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.
您可以使用
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 to0
, 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 to0
.我必须为自定义 Akka 路由逻辑创建一个类似的循环自动收报机,该逻辑必须与默认的不同,以避免网络开销,因为我的逻辑只是选择下一个路由。
注意:从建议的 Java 8 实现中复制:
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:
对于由多个线程并行递增的高强度循环计数器,我建议使用 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 insideStriped64.java
), because it is more scalable compared toAtomicLong
. It is easy to adapt it to the above solutions.It is assumed that
get
operation is not so frequent inLongAdder
. When callingcounter.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.在
AtomicInteger
上实现这样的计数器很容易:It is easy to implement such a counter atop
AtomicInteger
: