代码之家  ›  专栏  ›  技术社区  ›  wishihadabettername

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

  •  33
  • wishihadabettername  · 技术社区  · 16 年前

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

    增量将是并行的,并且经常出现。读取(转储统计数据)是一种罕见的操作。

    我想用 ConcurrentHashMap .问题是如何 定期的加薪 计数器正确。由于映射没有“增量”操作,我需要先读取当前值,然后将其增量,然后将新值放入映射中。如果没有更多的代码,这就不是一个原子操作。

    是否可以在没有同步的情况下实现这一点(这将破坏 ConcurrentHashMap )?我需要看看吗 Guava ?

    谢谢你的指点。


    P、 S。
    有一个相关的问题( Most efficient way to increment a Map value in Java )但关注的是性能,而不是多线程

    使现代化
    对于那些通过搜索同一主题来到这里的人:除了下面的答案之外,还有一个有用的 presentation 顺便提一句,它涵盖了相同的主题。请参见幻灯片24-33。

    7 回复  |  直到 9 年前
        1
  •  35
  •   ZhekaKozlov    12 年前

    在Java 8中:

    ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>();
    
    map.computeIfAbsent("key", k -> new LongAdder()).increment();
    
        2
  •  21
  •   ZhekaKozlov    10 年前

    番石榴的新品种 AtomicLongMap (在版本11中)可能会满足这一需求。

        3
  •  9
  •   Steven Schlansker    16 年前

    你很接近。你为什么不试试 ConcurrentHashMap<Key, AtomicLong> ? 如果您的 Key s(指标)是不变的,您甚至可以使用标准 HashMap (如果是只读的,它们是线程安全的,但最好使用 ImmutableMap 来自Google Collections或 Collections.unmodifiableMap 等)。

    这样,您可以使用 map.get(myKey).incrementAndGet() 以凹凸统计信息。

        4
  •  6
  •   Tom Hawtin - tackline    16 年前

    除了与 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 可能会被“缓存”。对于较长的值,可能需要分配。但分配速度实际上非常快(您可以进一步缓存),这取决于您在最坏情况下的预期。

        5
  •  1
  •   Vitalii    14 年前

    有必要这样做。 我使用的是ConcurrentHashMap+AtomicInteger。 此外,还为原子刷新引入了ReentrantRW锁(非常类似的行为)。

    用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();
        }
    
    }
    
        6
  •  1
  •   f.ald    7 年前

    我做了一个基准测试来比较 LongAdder 和 AtomicLong 。

    长加法器 在我的基准测试中有更好的性能:对于使用大小为100的map的500次迭代(10个并发线程),LongAdder的平均时间是1270ms,而AtomicLong的平均时间是1315ms。

    推荐文章