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
- Evalúa la necesidad real: No uses colecciones sincronizadas si no hay concurrencia.
- Prefiere
java.util.concurrent: Más eficiente en operaciones complejas. - 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.concurrentcomoConcurrentHashMap, 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
ConcurrentModificationExceptiono 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
synchronizedMapes mucho más lento que unConcurrentHashMapen escrituras concurrentes. - Alternativas:
- Para lecturas frecuentes:
CopyOnWriteArrayList. - Para mapas:
ConcurrentHashMap. - Para colas:
LinkedBlockingQueue.
- Para lecturas frecuentes:
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.