Java Language
Colecciones concurrentes
Buscar..
Introducción
Colecciones a prueba de hilos
De forma predeterminada, los distintos tipos de Colección no son seguros para subprocesos.
Sin embargo, es bastante fácil hacer una colección segura para subprocesos.
List<String> threadSafeList = Collections.synchronizedList(new ArrayList<String>());
Set<String> threadSafeSet = Collections.synchronizedSet(new HashSet<String>());
Map<String, String> threadSafeMap = Collections.synchronizedMap(new HashMap<String, String>());
Cuando realice una colección segura para subprocesos, nunca debe acceder a ella a través de la colección original, solo a través de la envoltura segura para hilos.
A partir de Java 5, java.util.collections
tiene varias colecciones nuevas seguras para subprocesos que no necesitan los diversos métodos de Collections.synchronized
.
List<String> threadSafeList = new CopyOnWriteArrayList<String>();
Set<String> threadSafeSet = new ConcurrentHashSet<String>();
Map<String, String> threadSafeMap = new ConcurrentHashMap<String, String>();
Colecciones concurrentes
Las colecciones concurrentes son una generalización de las colecciones seguras para subprocesos, que permiten un uso más amplio en un entorno concurrente.
Si bien las colecciones seguras para subprocesos tienen la adición o eliminación segura de elementos de varios subprocesos, no necesariamente tienen una iteración segura en el mismo contexto (es posible que uno no pueda recorrer la colección en un subproceso en una secuencia, mientras que otro lo modifica agregando quitando elementos).
Aquí es donde se utilizan las colecciones concurrentes.
Como la iteración suele ser la implementación básica de varios métodos masivos en colecciones, como addAll
, removeAll
, o también la copia de colecciones (a través de un constructor u otros medios), clasificación, ... el caso de uso de colecciones concurrentes es en realidad bastante grande.
Por ejemplo, Java SE 5 java.util.concurrent.CopyOnWriteArrayList
es una implementación de Lis
t segura para subprocesos, su javadoc afirma:
El método del iterador de estilo "instantánea" utiliza una referencia al estado de la matriz en el punto en que se creó el iterador. Esta matriz nunca cambia durante la vida útil del iterador, por lo que la interferencia es imposible y se garantiza que el iterador no lanzará ConcurrentModificationException.
Por lo tanto, el siguiente código es seguro:
public class ThreadSafeAndConcurrent {
public static final List<Integer> LIST = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
Thread modifier = new Thread(new ModifierRunnable());
Thread iterator = new Thread(new IteratorRunnable());
modifier.start();
iterator.start();
modifier.join();
iterator.join();
}
public static final class ModifierRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 50000; i++) {
LIST.add(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static final class IteratorRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 10000; i++) {
long total = 0;
for(Integer inList : LIST) {
total += inList;
}
System.out.println(total);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Otra colección concurrente con respecto a la iteración es ConcurrentLinkedQueue
, que establece:
Los iteradores son débilmente consistentes, devolviendo elementos que reflejan el estado de la cola en algún momento en o desde la creación del iterador. No lanzan java.util.ConcurrentModificationException, y pueden proceder simultáneamente con otras operaciones. Los elementos contenidos en la cola desde la creación del iterador se devolverán exactamente una vez.
Uno debe revisar los javadocs para ver si una colección es concurrente o no. Los atributos del iterador devueltos por el método iterator()
("falla rápidamente", "débilmente consistente", ...) es el atributo más importante que debe buscarse.
Hilo seguro pero ejemplos no concurrentes
En el código anterior, cambiando la declaración LIST
a
public static final List<Integer> LIST = Collections.synchronizedList(new ArrayList<>());
Podría (y estadísticamente lo hará en la mayoría de las arquitecturas de CPU / núcleo con múltiples CPU) dar lugar a excepciones.
Las colecciones sincronizadas de los métodos de utilidad de Collections
son seguras para subprocesos para la adición / eliminación de elementos, pero no la iteración (a menos que la colección subyacente que se le pasa ya lo sea).
Inserción en el mapa de hash concurrente
public class InsertIntoConcurrentHashMap
{
public static void main(String[] args)
{
ConcurrentHashMap<Integer, SomeObject> concurrentHashMap = new ConcurrentHashMap<>();
SomeObject value = new SomeObject();
Integer key = 1;
SomeObject previousValue = concurrentHashMap.putIfAbsent(1, value);
if (previousValue != null)
{
//Then some other value was mapped to key = 1. 'value' that was passed to
//putIfAbsent method is NOT inserted, hence, any other thread which calls
//concurrentHashMap.get(1) would NOT receive a reference to the 'value'
//that your thread attempted to insert. Decide how you wish to handle
//this situation.
}
else
{
//'value' reference is mapped to key = 1.
}
}
}