ConcurrentHashMap.merge în Java

Actualizează atomic valoarea unei chei: merge adaugă sau combină (contoare), compute calculează valoarea nouă din cea veche.

Sintaxă
V merge(K cheie, V valoare, BiFunction<? super V, ? super V, ? extends V> combina)
V compute(K cheie, BiFunction<? super K, ? super V, ? extends V> functie)

Ce face ConcurrentHashMap.merge

merge(cheie, valoare, combina): dacă cheia nu există, pune valoare; dacă există, pune combina(valoareVeche, valoare). Cu Integer::sum obții un contor: harta.merge(cuvant, 1, Integer::sum).

compute(cheie, (k, vechi) -> nou) primește cheia și valoarea veche (null dacă lipsește) și returnează valoarea nouă. Dacă funcția returnează null, cheia este ștearsă.

În ConcurrentHashMap, ambele metode sunt atomice: niciun alt fir nu poate modifica aceeași cheie în timpul calculului, deci nu se pierd actualizări. Funcția trebuie să fie scurtă și să nu modifice alte chei ale aceleiași hărți.

Parametri

Parametru Descriere
cheie Cheia actualizată.
valoare Pentru merge: valoarea pusă dacă cheia lipsește și al doilea argument pentru combina.
combina / functie Calculează valoarea nouă; null înseamnă ștergerea cheii.

Ce returnează

Valoarea nouă a cheii (sau null dacă a fost ștearsă).

Exemple

Exemplul 1: Numărarea voturilor din mai multe fire

Trei fire numără voturi în aceeași hartă. Cu merge nu se pierde niciun vot.

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        String[] candidati = {"Ana", "Bogdan", "Carmen"};
        Map<String, Integer> voturi = new ConcurrentHashMap<>();
        try (ExecutorService sectii = Executors.newFixedThreadPool(3)) {
            for (int s = 0; s < 3; s++) {
                sectii.submit(() -> {
                    for (int i = 0; i < 3000; i++) {
                        voturi.merge(candidati[i % 3], 1, Integer::sum);
                    }
                });
            }
        }
        System.out.println(new TreeMap<>(voturi));
    }
}

Rezultat

{Ana=3000, Bogdan=3000, Carmen=3000}

Exemplul 2: compute: stocul scade și cheia dispare la zero

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    static void vinde(Map<String, Integer> stoc, String produs) {
        stoc.compute(produs, (p, bucati) -> bucati == null || bucati <= 1 ? null : bucati - 1);
    }

    public static void main(String[] args) {
        Map<String, Integer> stoc = new ConcurrentHashMap<>(Map.of("caiet", 3, "pix", 1));
        vinde(stoc, "caiet");
        vinde(stoc, "pix");
        System.out.println(new TreeMap<>(stoc));
        stoc.compute("creion", (p, bucati) -> bucati == null ? 10 : bucati + 10);
        System.out.println(new TreeMap<>(stoc));
    }
}

Rezultat

{caiet=2}
{caiet=2, creion=10}

Exemplul 3: merge cu text: concatenare

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void main(String[] args) {
        Map<String, String> materii = new ConcurrentHashMap<>();
        materii.merge("Ana", "mate", (a, b) -> a + ", " + b);
        materii.merge("Ana", "română", (a, b) -> a + ", " + b);
        materii.merge("Mihai", "fizică", (a, b) -> a + ", " + b);
        System.out.println(new TreeMap<>(materii));
    }
}

Rezultat

{Ana=mate, română, Mihai=fizică}

Greșeli frecvente

  • Faci get, apoi put pentru un contor. Între cele două operații alt fir poate scrie; merge face totul atomic.
  • Din funcția lui compute modifici altă cheie a aceleiași hărți. Poate bloca programul sau arunca IllegalStateException.

Comenzi înrudite

  • ConcurrentHashMap: Un Map pe care mai multe fire îl pot citi și modifica în același timp, fără să se strice datele.
  • AtomicInteger.incrementAndGet: Crește atomic valoarea cu 1: incrementAndGet() returnează valoarea nouă (++x), getAndIncrement() pe cea veche (x++).
  • AtomicInteger: Un int pe care mai multe fire îl pot modifica sigur, fără synchronized: get(), set(), addAndGet() și altele.