Collections.synchronizedList în Java

Învelește o listă astfel încât fiecare metodă să poată fi apelată în siguranță din mai multe fire de execuție.

Sintaxă
static <T> List<T> synchronizedList(List<T> list)

// la fel: synchronizedSet, synchronizedMap

Ce face Collections.synchronizedList

Un ArrayList obișnuit nu suportă să fie modificat în același timp de mai multe fire de execuție (threads): pot dispărea elemente sau pot apărea excepții. Collections.synchronizedList(lista) returnează o vedere în care fiecare apel (add, get, size…) este protejat de un lacăt: un singur fir lucrează pe listă la un moment dat.

Parcurgerea nu este protejată automat. Când parcurgi lista (for-each, iterator, stream), pune bucla într-un bloc synchronized (lista) { ... }, altfel alt fir o poate modifica în timpul parcurgerii.

Pentru cod nou cu multe fire, clasele din java.util.concurrent sunt de obicei mai potrivite: CopyOnWriteArrayList (multe citiri, puține scrieri), ConcurrentHashMap, ConcurrentLinkedQueue.

Parametri

Parametru Descriere
list Lista care trebuie protejată. După înveliș, folosește doar vederea.

Ce returnează

O vedere sincronizată a listei.

Exemple

Exemplul 1: Patru fire adaugă în aceeași listă

Fiecare fir adaugă 1000 de numere. Cu lista sincronizată, la final sunt exact 4000. join() așteaptă terminarea firelor.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> comenzi = Collections.synchronizedList(new ArrayList<>());
        List<Thread> fire = new ArrayList<>();
        for (int f = 0; f < 4; f++) {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 1000; i++) {
                    comenzi.add(i);
                }
            });
            fire.add(t);
            t.start();
        }
        for (Thread t : fire) {
            t.join();
        }
        System.out.println("Comenzi înregistrate: " + comenzi.size());
    }
}

Rezultat

Comenzi înregistrate: 4000

Exemplul 2: Parcurgerea se face într-un bloc synchronized

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> jurnal = Collections.synchronizedList(new ArrayList<>());
        jurnal.add("08:00 pornit");
        jurnal.add("08:05 conectat la baza de date");
        jurnal.add("08:06 gata");
        synchronized (jurnal) {
            for (String linie : jurnal) {
                System.out.println(linie);
            }
        }
    }
}

Rezultat

08:00 pornit
08:05 conectat la baza de date
08:06 gata

Greșeli frecvente

  • Parcurgi lista sincronizată fără synchronized (lista) în timp ce alte fire scriu. Poate apărea ConcurrentModificationException.
  • Păstrezi și folosești în continuare lista originală, neînvelită. Accesul prin ea nu este protejat.
  • Crezi că două apeluri la rând sunt atomice: if (!lista.contains(x)) lista.add(x);. Alt fir poate interveni între ele; pune ambele într-un bloc synchronized (lista).

Comenzi înrudite

  • Collections.unmodifiableList: Returnează o vedere doar-pentru-citire a unei liste: cine o primește nu o poate modifica, dar schimbările originalului se văd.
  • ArrayList: Lista implementată cu un array intern care crește automat; acces foarte rapid după index. Cea mai folosită listă din Java.
  • Thread: Un fir de execuție: cod care rulează în paralel cu restul programului. start() îl pornește, join() așteaptă să se termine.
  • HashMap: Dicționarul cel mai rapid: put, get și remove în timp aproape constant, dar fără ordine garantată a cheilor.