我想从网络应用中的各个地方收集一些指标。为了简单起见,所有这些都是计数器,因此唯一的修饰符操作是将它们递增1.
增量将是并发且经常的。读取(转储统计数据)是一种罕见的操作。
我正在考虑使用 ConcurrentHashMap 。问题是如何正确递增计数器。由于地图没有“增量”操作,我需要首先读取当前值,增加它而不是将新值放在地图中。没有更多代码,这不是原子操作。
是否有可能在没有同步的情况下实现这一点(这会破坏 ConcurrentHashMap 的目的)?我需要查看Guava吗?
感谢您的任何指示。
P.S。
关于SO(Most efficient way to increment a Map value in Java)有一个相关问题,但侧重于性能而不是多线程
更新
对于那些通过搜索相同主题到达此处的人:除了下面的答案之外,还有一个有用的presentation,它偶然涵盖了相同的主题。见幻灯片24-33。
答案 0 :(得分:33)
在Java 8中:
ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key", k -> new LongAdder()).increment();
答案 1 :(得分:18)
Guava的新AtomicLongMap(在第11版中)可能会满足这种需求。
答案 2 :(得分:8)
你非常接近。你为什么不试试像ConcurrentHashMap<Key, AtomicLong>
这样的东西?
如果您的Key
s(指标)不变,您甚至可以使用标准HashMap
(如果只读,它们是线程安全的,但建议您使用{{1}进行明确说明来自Google Collections或ImmutableMap
等)。
这样,您就可以使用Collections.unmodifiableMap
来统计数据。
答案 3 :(得分:4)
除了使用AtomicLong
之外,您可以执行通常的cas循环操作:
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.
}
(我多年没有写过do
- while
循环。)
对于小值,Long
可能会被“缓存”。对于更长的值,可能需要分配。但是分配实际上非常快(并且你可以进一步缓存) - 取决于你期望的,在最坏的情况下。
答案 4 :(得分:0)
有必要这样做。 我正在使用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();
}
}
答案 5 :(得分:0)
我做了一个基准测试,比较了LongAdder
和AtomicLong
的性能。
LongAdder
在我的基准测试中具有更好的性能:对于使用大小为100(10个并发线程)的映射进行500次迭代,LongAdder的平均时间为1270ms,而AtomicLong的平均时间为1315ms。
答案 6 :(得分:-3)
以下代码对我来说没有同步来计算单词&#39;频率
constructors