39

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

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

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

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

感谢您的任何指示。


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

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

4

6 回答 6

34

在 Java 8 中:

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

map.computeIfAbsent("key", k -> new LongAdder()).increment();
于 2014-10-06T10:37:56.560 回答
21

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

于 2012-01-20T16:15:32.157 回答
9

你很接近。你为什么不尝试类似的东西ConcurrentHashMap<Key, AtomicLong>?如果您Key的 s (指标)不变,您甚至可以只使用一个标准(如果它们是只读的,它们是线程安全的,但建议您使用来自 Google Collections 或等HashMap的明确说明)。ImmutableMapCollections.unmodifiableMap

这样,您可以使用map.get(myKey).incrementAndGet()统计数据。

于 2010-07-26T23:51:59.693 回答
6

Other than going with AtomicLong, you can do the usual cas-loop thing:

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

(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.

于 2010-07-27T00:18:39.853 回答
1

有必要做同样的事情。我正在使用 ConcurrentHashMap + AtomicInteger。此外,为原子刷新引入了 ReentrantRW Lock(非常相似的行为)。

使用 10 个键和每个键 10 个线程进行测试。什么都没有丢失。我只是还没有尝试过几个刷新线程,但希望它会起作用。

大规模的单用户模式刷新正在折磨我......我想删除 RWLock 并将刷新分解成小块。明天。

private ConcurrentHashMap<String,AtomicInteger> counters = new ConcurrentHashMap<String, AtomicInteger>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();

public void count(String invoker) {

    rwLock.readLock().lock();

    try{
        AtomicInteger currentValue = counters.get(invoker);
        // if entry is absent - initialize it. If other thread has added value before - we will yield and not replace existing value
        if(currentValue == null){
            // value we want to init with
            AtomicInteger newValue = new AtomicInteger(0);
            // try to put and get old
            AtomicInteger oldValue = counters.putIfAbsent(invoker, newValue);
            // if old value not null - our insertion failed, lets use old value as it's in the map
            // if old value is null - our value was inserted - lets use it
            currentValue = oldValue != null ? oldValue : newValue;
        }

        // counter +1
        currentValue.incrementAndGet();
    }finally {
        rwLock.readLock().unlock();
    }

}

/**
 * @return Map with counting results
 */
public Map<String, Integer> getCount() {
    // stop all updates (readlocks)
    rwLock.writeLock().lock();
    try{
        HashMap<String, Integer> resultMap = new HashMap<String, Integer>();
        // read all Integers to a new map
        for(Map.Entry<String,AtomicInteger> entry: counters.entrySet()){
            resultMap.put(entry.getKey(), entry.getValue().intValue());
        }
        // reset ConcurrentMap
        counters.clear();
        return resultMap;

    }finally {
        rwLock.writeLock().unlock();
    }

}
于 2012-08-26T00:48:37.517 回答
1

我做了一个基准来比较 和 的LongAdder性能AtomicLong

LongAdder在我的基准测试中有更好的性能:对于使用大小为 100 的映射(10 个并发线程)进行 500 次迭代,LongAdder 的平均时间为 1270 毫秒,而 AtomicLong 的平均时间为 1315 毫秒。

于 2019-01-30T12:12:35.780 回答