以原子方式递增存储在ConcurrentHashMap中的计数器

wis*_*ame 35 java concurrency multithreading concurrenthashmap guava

我想从网络应用中的各个地方收集一些指标.为了简单起见,所有这些都是计数器,因此唯一的修饰符操作是将它们递增1.

增量将是并发的并且经常是.读取(转储统计信息)是一种罕见的操作.

我在考虑使用ConcurrentHashMap.问题是如何正确递增计数器.由于地图没有"增量"操作,我需要首先读取当前值,增加它而不是将新值放在地图中.没有更多代码,这不是原子操作.

是否有可能在没有同步的情况下实现这一点(这会破坏ConcurrentHashMap的目的)?我需要看看番石榴吗?

谢谢你的任何指示.


PS
有一个关于SO的相关问题(在Java中增加Map值的最有效方法)但是侧重于性能而不是多线程

更新
对于那些通过搜索同一主题到达这里的人:除了下面的答案之外,还有一个有用的演示文稿,它偶然涵盖了相同的主题.见幻灯片24-33.

Zhe*_*lov 33

在Java 8中:

ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>();

map.computeIfAbsent("key", k -> new LongAdder()).increment();
Run Code Online (Sandbox Code Playgroud)


Lou*_*man 19

Guava的新AtomicLongMap(在第11版中)可能会满足这种需求.


Ste*_*ker 8

你很亲密.你为什么不试试像ConcurrentHashMap<Key, AtomicLong>?如果您的Keys(指标)不变,您甚至可以只使用一个标准HashMap(如果只读它们就是线程安全的,但是建议您使用ImmutableMapGoogle Collections或其他方式明确说明这一点Collections.unmodifiableMap).

这样,您就可以map.get(myKey).incrementAndGet()用来计算统计数据.


Tom*_*ine 6

除了 with AtomicLong,你可以做通常的 cas-loop 事情:

private final ConcurrentMap<Key,Long> counts =
    new ConcurrentHashMap<Key,Long>();

public void increment(Key key) {
    if (counts.putIfAbsent(key, 1)) == null) {
        return;
    }

    Long old;
    do {
       old = counts.get(key);
    } while (!counts.replace(key, old, old+1)); // Assumes no removal.
}
Run Code Online (Sandbox Code Playgroud)

(我已经很久没有写do-while循环了。)

对于小值,Long可能会被“缓存”。对于更长的值,它可能需要分配。但是分配实际上非常快(并且您可以进一步缓存) - 在最坏的情况下取决于您的期望。