我想从网络应用程序的各个地方收集一些指标。为了简单起见,所有这些都是计数器,因此唯一的修饰符操作是将它们加 1。
增量将是并发的并且经常发生。读取(转储统计数据)是一种罕见的操作。
我正在考虑使用ConcurrentHashMap。问题是如何正确地增加计数器。由于地图没有“增量”操作,因此我需要先读取当前值,对其进行增量,然后将新值放入地图中。如果没有更多代码,这不是原子操作。
是否可以在不同步的情况下实现这一点(这会违背ConcurrentHashMap的目的)?我需要查看 Guava 吗?
感谢您的指点。
PS
SO 有一个相关问题(最有效的方法在 Java 中增加 Map 值),但关注性能而不是多线程
更新
对于那些通过搜索同一主题到达这里的人:除了下面的答案之外,还有一个有用的 演示文稿 顺便涵盖了同一主题。参见幻灯片 24-33。
I would like to collect some metrics from various places in a web app. To keep it simple, all these will be counters and therefore the only modifier operation is to increment them by 1.
The increments will be concurrent and often. The reads (dumping the stats) is a rare operation.
I was thinking to use a ConcurrentHashMap. The issue is how to increment the counters correctly. Since the map doesn't have an "increment" operation, I need to read the current value first, increment it than put the new value in the map. Without more code, this is not an atomic operation.
Is it possible to achieve this without synchronization (which would defeat the purpose of the ConcurrentHashMap)? Do I need to look at Guava ?
Thanks for any pointers.
P.S.
There is a related question on SO (Most efficient way to increment a Map value in Java) but focused on performance and not multi-threading
UPDATE
For those arriving here through searches on the same topic: besides the answers below, there's a useful presentation which incidentally covers the same topic. See slides 24-33.
发布评论
评论(6)
在 Java 8 中:
In Java 8:
Guava 的新 AtomicLongMap(在版本 11 中)可能会满足此需求。
Guava's new AtomicLongMap (in release 11) might address this need.
你已经很接近了。为什么不尝试类似
ConcurrentHashMap
的东西呢?如果您的
Key
(指标)不变,您甚至可以只使用标准的HashMap
(如果只读,它们是线程安全的,但建议您明确说明使用来自 Google Collections 的ImmutableMap
或Collections.unmodifyingMap
等)。这样,您就可以使用
map.get(myKey).incrementAndGet()
来获取统计信息。You're pretty close. Why don't you try something like a
ConcurrentHashMap<Key, AtomicLong>
?If your
Key
s (metrics) are unchanging, you could even just use a standardHashMap
(they are threadsafe if readonly, but you'd be well advised to make this explicit with anImmutableMap
from Google Collections orCollections.unmodifiableMap
, etc.).This way, you can use
map.get(myKey).incrementAndGet()
to bump statistics.除了使用
AtomicLong
之外,您还可以执行通常的 cas-loop 操作:(我已经很久没有编写
do
-while
循环了.)对于较小的值,
Long
可能会被“缓存”。对于较长的值,可能需要分配。但分配实际上非常快(并且您可以进一步缓存) - 取决于您在最坏情况下的期望。Other than going with
AtomicLong
, you can do the usual cas-loop thing:(I've not written a
do
-while
loop for ages.)For small values the
Long
will probably be "cached". For longer values, it may require allocation. But the allocations are actually extremely fast (and you can cache further) - depends upon what you expect, in the worst case.有必要做同样的事情。
我正在使用 ConcurrentHashMap + AtomicInteger。
此外,还引入了 ReentrantRW Lock 来实现原子刷新(非常相似的行为)。
使用 10 个键和每个键 10 个线程进行测试。什么都没有丢失。
我还没有尝试过几个冲洗线程,但希望它能起作用。
大规模的单用户模式刷新正在折磨我......
我想删除 RWLock 并将冲洗分解成小块。明天。
Got a necessity to do the same.
I'm using ConcurrentHashMap + AtomicInteger.
Also, ReentrantRW Lock was introduced for atomic flush(very similar behavior).
Tested with 10 Keys and 10 Threads per each Key. Nothing was lost.
I just haven't tried several flushing threads yet, but hope it will work.
Massive singleusermode flush is torturing me...
I want to remove RWLock and break down flushing into small pieces. Tomorrow.
我做了一个基准测试来比较 LongAdder 和 AtomicLong 的性能。
LongAdder
在我的基准测试中具有更好的性能:对于使用大小为 100 的映射(10 个并发线程)的 500 次迭代,LongAdder 的平均时间为 1270 毫秒,而 AtomicLong 的平均时间为 1315 毫秒。I did a benchmark to compare the performance of
LongAdder
andAtomicLong
.LongAdder
had a better performance in my benchmark: for 500 iterations using a map with size 100 (10 concurrent threads), the average time for LongAdder was 1270ms while that for AtomicLong was 1315ms.