Concurrencia
Java proporciona colecciones thread-safe para entornos donde múltiples hilos acceden a datos compartidos.
¿Por qué colecciones thread-safe?
// ❌ HashMap NO es thread-safe
Map<String, Integer> map = new HashMap<>();
// Problema: ConcurrentModificationException o datos corruptos
// cuando múltiples threads acceden simultáneamente
Colecciones Concurrentes
CopyOnWriteArrayList
Lista que copia todo el array en cada modificación. Ideal para lecturas frecuentes, escrituras raras.
List<String> lista = new CopyOnWriteArrayList<>();
// Múltiples threads pueden leer simultáneamente sin bloqueo
// Escrituras crean una copia del array interno
lista.add("elemento"); // Crea copia del array
for (String s : lista) {
// Itera sobre snapshot (no ConcurrentModificationException)
System.out.println(s);
}
Cuándo usar: - Lecturas frecuentes, escrituras raras - Tamaño pequeño a mediano - No necesitas consistencia inmediata en iteración
ConcurrentHashMap
Map thread-safe con alta concurrencia. Divide el mapa en segmentos para reducir bloqueos.
Map<String, Integer> map = new ConcurrentHashMap<>();
// Operaciones atómicas
map.put("clave", 1);
map.putIfAbsent("clave", 2); // No sobrescribe si existe
map.computeIfAbsent("clave2", k -> calcularValor(k));
// Iteración segura
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
Operaciones atómicas:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// compute: actualiza valor basado en key y valor actual
map.compute("contador", (k, v) -> v == null ? 1 : v + 1);
// merge: similar a compute pero con valor por defecto
map.merge("clave", 1, Integer::sum);
// computeIfAbsent: computa solo si no existe
map.computeIfAbsent("clave", k -> expensiveOperation());
// computeIfPresent: computa solo si existe
map.computeIfPresent("clave", (k, v) -> v * 2);
CopyOnWriteArraySet
Set basado en CopyOnWriteArrayList. Misma semántica: lecturas rápidas, escrituras lentas.
Set<String> set = new CopyOnWriteArraySet<>();
set.add("elemento");
// Iteración segura
for (String s : set) {
System.out.println(s);
}
ConcurrentLinkedQueue
Queue no bloqueante, basada en nodos enlazados. Alto rendimiento para operaciones concurrentes.
Queue<String> queue = new ConcurrentLinkedQueue<>();
// Productor
queue.offer("tarea1");
queue.offer("tarea2");
// Consumidor
String tarea = queue.poll(); // No bloquea, retorna null si vacía
BlockingQueue
Interfaz para colas bloqueantes (implementaciones: ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue).
BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
// Productor (bloquea si la cola está llena)
queue.put("elemento");
// Consumidor (bloquea si la cola está vacía)
String elemento = queue.take();
// Métodos no bloqueantes con timeout
boolean success = queue.offer("elemento", 5, TimeUnit.SECONDS);
String elem = queue.poll(5, TimeUnit.SECONDS);
Synchronized Collections
Wrappers que agregan sincronización a colecciones no thread-safe.
// Crear colecciones sincronizadas
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// ⚠️ Iteración requiere sincronización manual
synchronized (syncList) {
for (String s : syncList) {
System.out.println(s);
}
}
Comparativa
| Colección | Thread-safe | Estrategia | Rendimiento lectura | Rendimiento escritura |
|---|---|---|---|---|
| HashMap | ❌ | - | Alta | Alta |
| ConcurrentHashMap | ✅ | Segment locking | Muy alta | Alta |
| Collections.synchronizedMap | ✅ | Lock global | Baja | Baja |
| ArrayList | ❌ | - | Alta | Alta |
| CopyOnWriteArrayList | ✅ | Copy-on-write | Muy alta | Baja |
| Vector | ✅ | Lock global | Baja | Baja |
Implementación en el proyecto
// Ejemplo de caché concurrente
public class ConcurrentCache<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
public V get(K key) {
return cache.computeIfAbsent(key, this::loadFromDatabase);
}
public void put(K key, V value) {
cache.put(key, value);
}
private V loadFromDatabase(K key) {
// Operación costosa
return database.load(key);
}
}
Escenarios BDD
Escenario: ConcurrentHashMap permite acceso concurrente
Dado un ConcurrentHashMap vacío
Cuando múltiples threads agregan elementos
Entonces todos los elementos se agregan sin excepciones
Escenario: CopyOnWriteArrayList permite lectura concurrente
Dado una CopyOnWriteArrayList con elementos
Cuando un thread itera y otro modifica
Entonces la iteración no lanza excepción
Recursos Adicionales
Thread-Safe Collections - Proyecto Complementario
Para una exploración más profunda de colecciones thread-safe, incluyendo implementaciones adicionales y patrones avanzados de concurrencia, visita:
Este proyecto complementario cubre: - Implementaciones thread-safe adicionales - Patrones de diseño concurrente - Ejemplos avanzados de sincronización - Mejores prácticas para entornos multi-hilo
Best Practices
- Prefiere ConcurrentHashMap sobre
Collections.synchronizedMap() - CopyOnWrite solo cuando escrituras son raras
- Iteración: Usar iteradores concurrentes o snapshot
- Atomicidad: Usar métodos atómicos (
compute,merge,putIfAbsent) - Evita synchronized: Las colecciones concurrentes modernas son más eficientes
Conclusión
Para código concurrente, usa colecciones diseñadas para ello. ConcurrentHashMap y CopyOnWriteArrayList cubren la mayoría de casos con excelente rendimiento.