Copyonwritearraylist

CopyonWriteArrayList

Documentación: CopyOnWriteArrayList en Java


Introducción

CopyOnWriteArrayList es una implementación de la interfaz List diseñada para entornos concurrentes, donde múltiples hilos acceden y modifican datos simultáneamente. Pertenece al paquete java.util.concurrent y garantiza que las operaciones de lectura no se vean afectadas por operaciones de escritura, ofreciendo seguridad en hilos (thread-safe) sin bloqueos explícitos.


¿Cómo funciona?

Mecanismo de Copia en Escritura (Copy-On-Write)

  • Lecturas (Operaciones de Consulta):

    Cuando un hilo realiza una lectura, trabaja con una instantánea (snapshot) inmutable del array subyacente. Esto asegura que los lectores siempre vean una versión consistente de los datos, incluso si otros hilos están modificando la lista.

  • Escrituras (Operaciones de Modificación):

    Cuando un hilo modifica la lista (añadir, eliminar, actualizar), se crea una copia del array interno. La modificación se aplica a esta copia, y una vez completada, la copia reemplaza al array original. Este proceso es atómico y sincronizado.


Características Clave

  1. Seguridad en Hilos (Thread-Safety):

    Ideal para escenarios con más lecturas que escrituras. Los lectores no necesitan sincronización, mientras que las escrituras son sincronizadas para evitar condiciones de carrera.

  2. Iteradores Inmutables:

    Los iteradores (IteratorListIterator) trabajan con la instantánea del array al momento de su creación. No reflejarán cambios posteriores y nunca lanzarán ConcurrentModificationException.

  3. Consistencia Eventual:

    Los cambios realizados por escritores no son inmediatamente visibles para los lectores activos, pero sí lo serán para nuevas lecturas una vez actualizado el array.

  4. Rendimiento:

    • ✅ Excelente para lecturas frecuentes (operaciones O(1).
    • ❌ Costoso para escrituras frecuentes debido a la copia del array (O(n).

Analogía con Git

CopyOnWriteArrayList funciona de manera similar a las ramas en Git:

  • Operaciones de Lectura: Crean una rama (instantánea) de la rama principal para trabajar sin afectar cambios futuros.
  • Operaciones de Escritura: Modifican una copia (nueva rama) y fusionan los cambios a la rama principal al finalizar, asegurando coherencia.

Código de Ejemplo

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;

class ReaderTask implements Runnable {
    private final List<Integer> list;

    public ReaderTask(List<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println("Leyendo lista: " + list);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

class WriterTask implements Runnable {
    private final List<Integer> list;
    private final ThreadLocalRandom random = ThreadLocalRandom.current();

    public WriterTask(List<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1200);
                int index = random.nextInt(list.size());
                int value = random.nextInt(10);
                list.set(index, value);
                System.out.println("Escrito " + value + " en índice " + index);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

public class Simulation {
    private final List<Integer> list = new CopyOnWriteArrayList<>();

    public Simulation() {
        // Inicializar con ceros
        for (int i = 0; i < 5; i++) {
            list.add(0);
        }
    }

    public void simulate() {
        // 3 hilos de escritura y 1 de lectura
        for (int i = 0; i < 3; i++) {
            new Thread(new WriterTask(list)).start();
        }
        new Thread(new ReaderTask(list)).start();
    }

    public static void main(String[] args) {
        Simulation simulation = new Simulation();
        simulation.simulate();
    }
}

Explicación del Código

  • Tarea de Lectura (ReaderTask): Lee la lista cada segundo e imprime su estado.
  • Tarea de Escritura (WriterTask): Modifica un índice aleatorio de la lista cada 1.2 segundos.
  • Simulación: Crea una lista inicializada con ceros y lanza tres hilos de escritura y uno de lectura.

Mejores Prácticas

  1. Úsalo Cuando:
    • Las lecturas son mucho más frecuentes que las escrituras.
    • Necesitas iteradores que no fallen por modificaciones concurrentes.
  2. Evítalo Cuando:
    • Las escrituras son muy frecuentes (usa ConcurrentHashMap o estructuras bloqueantes).
    • El espacio en memoria es crítico (las copias pueden ser costosas).

Alternativas

  • Collections.synchronizedList:

    Ofrece sincronización explícita pero bloquea toda la lista durante escrituras/lecturas.

  • ConcurrentHashMap:

    Mejor rendimiento para entornos con altas concurrencias y acceso por claves.

  • ReentrantReadWriteLock:

    Permite múltiples lectores o un escritor, útil para control granular.


Conclusión

CopyOnWriteArrayList es una herramienta poderosa para escenarios de lectura dominante en aplicaciones concurrentes. Su diseño sin bloqueos para lecturas y el uso de instantáneas garantizan consistencia y seguridad, aunque a costa de un mayor uso de memoria en escrituras frecuentes. Siempre evalúa el balance entre lecturas/escrituras en tu caso de uso antes de elegir esta estructura.