ConcurrentHashMap în Java

Un Map pe care mai multe fire îl pot citi și modifica în același timp, fără să se strice datele.

Sintaxă
import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<K, V> harta = new ConcurrentHashMap<>();
V putIfAbsent(K cheie, V valoare)
V computeIfAbsent(K cheie, Function<? super K, ? extends V> functie)

Ce face ConcurrentHashMap

HashMap obișnuit nu este sigur când mai multe fire scriu în el odată: se pot pierde valori sau structura internă se poate strica. ConcurrentHashMap implementează aceeași interfață Map, dar permite citiri și scrieri simultane din mai multe fire.

O singură operație (put, get, remove) este sigură, dar o secvență ca „citește, apoi scrie” nu este: între cele două pași, alt fir poate schimba valoarea. Pentru astfel de actualizări folosește metodele atomice: merge, compute, computeIfAbsent, putIfAbsent.

Spre deosebire de HashMap, nu acceptă chei sau valori null. Ordinea elementelor nu este garantată; pentru afișare sortată copiază-l într-un TreeMap.

Ce returnează

Un Map sigur pentru mai multe fire.

Exemple

Exemplul 1: Mai multe fire adaugă în aceeași hartă

Patru fire adaugă câte 250 de chei diferite. Toate cele 1000 de chei ajung în hartă.

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

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> comenzi = new ConcurrentHashMap<>();
        try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
            for (int fir = 0; fir < 4; fir++) {
                int start = fir * 250;
                executor.submit(() -> {
                    for (int i = start; i < start + 250; i++) {
                        comenzi.put(i, "comanda-" + i);
                    }
                });
            }
        }
        System.out.println("Comenzi salvate: " + comenzi.size());
        System.out.println("Comanda 777: " + comenzi.get(777));
    }
}

Rezultat

Comenzi salvate: 1000
Comanda 777: comanda-777

Exemplul 2: computeIfAbsent: o listă pe fiecare oraș

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

public class Main {
    public static void main(String[] args) {
        Map<String, List<String>> clientiPeOras = new ConcurrentHashMap<>();
        String[][] clienti = {{"Ana", "Iași"}, {"Mihai", "Cluj"}, {"Ioana", "Iași"}, {"Radu", "Brașov"}};
        for (String[] c : clienti) {
            clientiPeOras.computeIfAbsent(c[1], oras -> new CopyOnWriteArrayList<>()).add(c[0]);
        }
        System.out.println(new TreeMap<>(clientiPeOras));
    }
}

Rezultat

{Brașov=[Radu], Cluj=[Mihai], Iași=[Ana, Ioana]}

Exemplul 3: null nu este permis

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

public class Main {
    public static void main(String[] args) {
        Map<String, String> simplu = new HashMap<>();
        simplu.put("telefon", null);
        System.out.println("HashMap acceptă null: " + simplu);
        Map<String, String> concurent = new ConcurrentHashMap<>();
        try {
            concurent.put("telefon", null);
        } catch (NullPointerException e) {
            System.out.println("ConcurrentHashMap aruncă NullPointerException");
        }
    }
}

Rezultat

HashMap acceptă null: {telefon=null}
ConcurrentHashMap aruncă NullPointerException

Greșeli frecvente

  • Scrii harta.put(k, harta.get(k) + 1) din mai multe fire. Două fire pot citi aceeași valoare și o actualizare se pierde; folosește merge(k, 1, Integer::sum).
  • Folosești Collections.synchronizedMap pentru multe fire. Blochează toată harta la fiecare operație; ConcurrentHashMap este mai rapid.

Comenzi înrudite

  • ConcurrentHashMap.merge: Actualizează atomic valoarea unei chei: merge adaugă sau combină (contoare), compute calculează valoarea nouă din cea veche.
  • AtomicInteger: Un int pe care mai multe fire îl pot modifica sigur, fără synchronized: get(), set(), addAndGet() și altele.
  • ReentrantLock: Un lacăt: lock() lasă un singur fir să intre în zona protejată, unlock() îl eliberează; unlock se pune mereu în finally.
  • ExecutorService: Un grup de fire de execuție care primește sarcini și le rulează; se oprește cu shutdown() și se așteaptă cu awaitTermination().