repetidos repetidas metodos elementos definicion con claves adicionar java multithreading concurrency guava concurrenthashmap

java - repetidas - Contadores de incremento atómico almacenados en ConcurrentHashMap



java map con claves repetidas (6)

Me gustaría recopilar algunas métricas de varios lugares en una aplicación web. Para mantenerlo simple, todos estos serán contadores y, por lo tanto, la única operación modificadora es incrementarlos en 1.

Los incrementos serán concurrentes y a menudo. Las lecturas (volcar las estadísticas) es una operación rara.

Estaba pensando en usar un mapa de fichas concurrente . El problema es cómo incrementar los contadores correctamente. Dado que el mapa no tiene una operación de "incremento", primero necesito leer el valor actual, incrementarlo para poner el nuevo valor en el mapa. Sin más código, esta no es una operación atómica.

¿Es posible lograr esto sin sincronización (lo que anularía el propósito de ConcurrentHashMap )? ¿Debo mirar a Guava ?

Gracias por cualquier punteros.

PD
Hay una pregunta relacionada sobre SO (la forma más eficiente de incrementar un valor de Map en Java ) pero enfocada en el rendimiento y no en multiproceso

ACTUALIZAR
Para aquellos que llegan aquí a través de búsquedas sobre el mismo tema: además de las respuestas a continuación, hay una presentation útil que incidentalmente cubre el mismo tema. Ver diapositivas 24-33.


Además de ir con AtomicLong , puedes hacer lo de cas-loop habitual:

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

(No he escrito un bucle do - while por años).

Para valores pequeños, el Long probablemente se "almacenará en caché". Para valores más largos, puede requerir asignación. Pero las asignaciones son en realidad extremadamente rápidas (y puede almacenar en caché aún más), depende de lo que espere, en el peor de los casos.


El nuevo AtomicLongMap Guava (en la versión 11) podría abordar esta necesidad.


El siguiente código funcionó para mí sin sincronización para contar las frecuencias de las palabras

protected void updateFreqCountForText(Map<String, Integer> wordFreq, String line) { ConcurrentMap<String, Integer> wordFreqConc = (ConcurrentMap<String, Integer>) wordFreq; Pattern pattern = Pattern.compile("[a-zA-Z]+"); Matcher matcher = pattern.matcher(line); while (matcher.find()) { String word = matcher.group().toLowerCase(); Integer oldCount; oldCount = wordFreqConc.get(word); if (oldCount == null) { oldCount = wordFreqConc.putIfAbsent(word, 1); if (oldCount == null) continue; else wordFreqConc.put(word, oldCount + 1); } else do { oldCount = wordFreqConc.get(word); } while (!wordFreqConc.replace(word, oldCount, oldCount + 1)); } }


En Java 8:

ConcurrentHashMap<String, LongAdder> map = new ConcurrentHashMap<>(); map.computeIfAbsent("key", k -> new LongAdder()).increment();


Estas bastante cerca ¿Por qué no intentas algo como un ConcurrentHashMap<Key, AtomicLong> ? Si sus Key s (métricas) no cambian, puede usar simplemente un HashMap estándar (son seguras para hilos si son de solo lectura, pero sería recomendable que lo explicite con ImmutableMap de Google Collections o Collections.unmodifiableMap , etc.) .

De esta forma, puede usar map.get(myKey).incrementAndGet() para encontrar estadísticas.


Tengo una necesidad de hacer lo mismo. Estoy usando ConcurrentHashMap + AtomicInteger. Además, se introdujo ReentrantRW Lock para descarga atómica (comportamiento muy similar).

Probado con 10 claves y 10 hilos por cada clave. Nada se perdió. Todavía no he probado varios hilos de lavado, pero espero que funcione.

El flujo masivo de modo único me está torturando ... Quiero eliminar RWLock y romper el lavado en pequeños pedazos. Mañana.

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(); } }