Introducción Synchronized Collections

Introducción Synchronized Collections

Introducción a Colecciones Sincronizadas en Java

Tabla de Contenidos

Tema Resumen
1. ¿Qué son? Colecciones seguras en entornos multi-hilo.
2. Métodos Sincronizados Métodos estáticos en Collections para crear colecciones sincronizadas.
3. Casos de Uso Escenarios donde se requiere acceso concurrente seguro.
4. Limitaciones Operaciones compuestas no son atómicas sin sincronización adicional.
5. Ventajas y Desventajas Thread-safe vs sobrecarga de rendimiento.
6. Ejemplos Prácticos Implementación de colecciones sincronizadas y buenas prácticas.
7. Alternativas Concurrentes Uso de java.util.concurrent para mayor eficiencia.

1. ¿Qué son las Colecciones Sincronizadas?

Son versiones thread-safe de las colecciones estándar de Java (como List, Set, Map), creadas mediante métodos de la clase java.util.Collections. Garantizan que las operaciones básicas (add, remove, etc.) sean atómicas y evitan corrupción de datos en hilos concurrentes.


2. Métodos Sincronizados Clave

Métodos estáticos en Collections para sincronizar colecciones:

Método Descripción
synchronizedCollection() Envuelve una Collection estándar.
synchronizedList(List<T> list) Crea una lista sincronizada.
synchronizedSet(Set<T> s) Crea un conjunto sincronizado.
synchronizedMap(Map<K, V> m) Crea un mapa sincronizado.
List<String> lista = new ArrayList<>();
List<String> listaSincronizada = Collections.synchronizedList(lista);

3. Casos de Uso Comunes

  • Entornos multi-hilo: Cuando múltiples hilos acceden/modifican una colección compartida.
  • Datos compartidos: Ejemplo: caché en memoria accedido por varios usuarios.

4. Limitaciones Importantes

  • Operaciones compuestas: No son atómicas sin sincronización externa.

    // Incorrecto: No sincronizado
    if (!listaSincronizada.contains(elemento)) {
        listaSincronizada.add(elemento);
    }
    
    // Correcto: Sincronización manual
    synchronized (listaSincronizada) {
        if (!listaSincronizada.contains(elemento)) {
            listaSincronizada.add(elemento);
        }
    }
  • Iteración: Requiere sincronización explícita al iterar.


5. Ventajas y Desventajas

Ventajas Desventajas
Thread-safe para operaciones básicas Sincronización a nivel de colección (puede ser ineficiente).
Fácil de implementar Rendimiento reducido en alta concurrencia.

6. Ejemplos Prácticos

Creación y Uso Básico

List<Integer> lista = Collections.synchronizedList(new ArrayList<>());

// Escritura segura desde múltiples hilos
synchronized (lista) {
    lista.add(42);
}

Iteración Sincronizada

List<String> listaSync = Collections.synchronizedList(new ArrayList<>());
// ...
synchronized (listaSync) {
    Iterator<String> it = listaSync.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

Comparación con Alternativas Concurrentes

// Usando ConcurrentHashMap (mejor para alta concurrencia)
Map<String, Integer> mapaConcurrente = new ConcurrentHashMap<>();

7. Alternativas en java.util.concurrent

Para escenarios de alta concurrencia, considera:

  • ConcurrentHashMap: Mapa con operaciones atómicas y segmentos bloqueados.
  • CopyOnWriteArrayList: Lista donde las modificaciones crean una copia interna.
  • BlockingQueue: Cola bloqueante para patrones productor-consumidor.

Mejores Prácticas

  1. Evalúa la necesidad real: No uses colecciones sincronizadas si no hay concurrencia.
  2. Prefiere java.util.concurrent: Más eficiente en operaciones complejas.
  3. Sincroniza bloques críticos: Para operaciones compuestas o iteraciones.
// Buen enfoque para operaciones compuestas
synchronized (miListaSincronizada) {
    miListaSincronizada.add(...);
    miListaSincronizada.remove(...);
}

Inconvenientes de Collections.synchronized

1. Coarse-Grained Locking (Bloqueo de Granularidad Gruesa)

  • Problema: Las colecciones sincronizadas usan un bloqueo a nivel de toda la colección para cada operación (add, remove, etc.).
    • Ejemplo: Si un hilo está modificando la colección, todos los demás hilos deben esperar, incluso si acceden a partes diferentes.
    • Consecuencia: Bajo alta concurrencia, esto genera contienda de hilos (thread contention) y reduce el rendimiento.
    • Alternativa: Colecciones de java.util.concurrent como ConcurrentHashMap, que usan bloqueos segmentados (lock striping) para mayor paralelismo.

2. Limited Functionality (Funcionalidad Limitada)

  • Problema: Solo las operaciones individuales (ej: add(), get()) están sincronizadas, no las operaciones compuestas.
    • Ejemplo: El clásico if (!list.contains(x)) { list.add(x); } no es atómico y puede fallar en hilos concurrentes.

    • Solución Requerida: Sincronización manual externa para operaciones compuestas:

      synchronized (listaSincronizada) {
          if (!listaSincronizada.contains(x)) {
              listaSincronizada.add(x);
          }
      }

3. No Fail-Fast Iterators (Iteradores No Fail-Fast)

  • Problema: Los iteradores de colecciones sincronizadas no son “fail-fast” por defecto al iterar en entornos multi-hilo.
    • Si un hilo modifica la colección mientras otro la itera, puede causar ConcurrentModificationException o comportamientos inesperados.

    • Solución Requerida: Bloquear manualmente la colección durante toda la iteración:

      synchronized (listaSincronizada) {
          for (String item : listaSincronizada) {
              // ...
          }
      }
    • Alternativa: Usar colecciones como CopyOnWriteArrayList, que tienen iteradores “snapshot” inmunes a modificaciones concurrentes.


4. Performance Overhead (Sobrecarga de Rendimiento)

  • Problema: La sincronización a nivel de método añade una sobrecarga significativa en escenarios de alta concurrencia.
    • Cada operación requiere adquirir y liberar un bloqueo, lo que es costoso en CPU.
    • Ejemplo: Un synchronizedMap es mucho más lento que un ConcurrentHashMap en escrituras concurrentes.
    • Alternativas:
      • Para lecturas frecuentes: CopyOnWriteArrayList.
      • Para mapas: ConcurrentHashMap.
      • Para colas: LinkedBlockingQueue.

Resumen Comparativo

Inconveniente Impacto Solución/Alternativa
Coarse-Grained Locking Contienda de hilos, bajo rendimiento Usar colecciones con bloqueo segmentado.
Limited Functionality Operaciones compuestas inseguras Sincronizar manualmente bloques críticos.
No Fail-Fast Iterators Riesgo de excepciones al iterar Bloquear la colección durante la iteración.
Performance Overhead Lentitud en alta concurrencia Migrar a colecciones de java.util.concurrent.

Conclusión: Las colecciones sincronizadas son útiles para escenarios simples de concurrencia, pero en entornos de alta demanda, las alternativas de java.util.concurrent ofrecen mejor rendimiento y funcionalidad más robusta.