39

Web アプリのさまざまな場所からいくつかのメトリックを収集したいと考えています。簡単にするために、これらはすべてカウンターになるため、唯一の修飾子操作はカウンターを 1 増やすことです。

増分は同時に頻繁に行われます。読み取り (統計のダンプ) はまれな操作です。

ConcurrentHashMapを使用することを考えていました。問題は、カウンターを正しくインクリメントする方法です。マップには「インクリメント」操作がないため、最初に現在の値を読み取り、新しい値をマップに入れるよりもインクリメントする必要があります。これ以上のコードがなければ、これはアトミック操作ではありません。

同期なしでこれを達成することは可能ですか (これはConcurrentHashMapの目的を無効にします)? Guavaを見る必要がありますか?

ご指摘ありがとうございます。


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>か?Keys (メトリクス) が変更されていない場合は、標準を使用することもできます(HashMap読み取り専用の場合はスレッドセーフですが、ImmutableMapGoogle Collections やCollections.unmodifiableMapなどからこれを明示的にすることをお勧めします)。

map.get(myKey).incrementAndGet()このようにして、統計をバンプするために使用できます。

于 2010-07-26T23:51:59.693 に答える
6

を使用する以外に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.
}

(私は何年もdo-whileループを書いていません。)

小さな値の場合、Longおそらく「キャッシュ」されます。より長い値の場合、割り当てが必要になる場合があります。しかし、割り当ては実際には非常に高速です(さらにキャッシュすることもできます)-最悪の場合、予想に依存します。

于 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 に答える