Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=afa65a6c-706b-4e79-8ccc-f8dd06f40cc3

Colecciones de datos thread-safe en Java

Imagen ornamental: la mascota de Java, Duke, sobrevolando una enorme madeja futurista de hilos de lana que protegen los datos en su interior. Por campusMVP.

Las colecciones de Java son un recurso fundamental a la hora de almacenar información por parte de las aplicaciones. Los datos alojados en una colección son accesibles a través de distintos mecanismos, dependiendo de que la estructura de datos subyacente sea una lista, árbol, tabla hash, etc., y pueden ser modificados, agregándose, eliminándose y cambiando su contenido.

Que una colección sea Thread-Safe significa que contempla adecuadamente la posibilidad del acceso concurrente a sus datos. En caso de que una colección no sea Thread-Safe, si se diera este acceso concurrente, el resultado sería imprevisible.

Imagina que un hilo está enumerando el contenido de una colección y, mientras tanto, otro hilo ha agregado o eliminado elementos de la misma. Lo habitual es que el primero genere una excepción, salvo que hayamos sincronizado de manera adecuada las operaciones mediante alguno de los procedimientos que proporciona la plataforma Java para ello.

Las versiones más recientes de la plataforma Java cuentan con varios tipos de colecciones, definidas en java.util.concurrent en lugar del paquete java.util más habitual, que están específicamente preparadas para operar en contextos de ejecución multihilo. Asimismo, cada tipo de colección existente en java.util dispone de una versión sincronizada. Son las colecciones thread-safe, cuyo uso veremos a continuación.

Colecciones sincronizadas en Java

La clase Collections cuenta con varios métodos estáticos cuyo nombre sigue el patrón synchronizedXXX(), siendo XXX un tipo de colección como puede ser List, Map, Set, etc. Estos métodos toman como argumento un objeto, la colección a sincronizar, y devuelven como resultado una referencia del tipo adecuado: List, Map, Set, etc:

Métodos synchronized en Collections

Lo que hacen esos métodos es crear un objeto de una clase interna a Collections, por ejemplo SynchronizedList. Esta se caracteriza por implementar la interfaz correspondiente (List en ese caso). Se limita a delegar las operaciones en la colección que se facilitó originalmente como parámetro, pero lo hace dentro de un bloque de código precedido por la palabra clave synchronized. De esta forma se asegura que la inserción, eliminación, cambios, etc., solo puedan llevarse a cabo por un hilo en cada momento.

Ejemplo de colección sincronizada en Java

Supongamos que vamos a usar una lista de tareas en una aplicación con múltiples hilos de ejecución, cada uno de los cuales puede agregar elementos a la misma. Para evitar problemas podríamos recurrir a una lista sincronizada, como se muestra en el siguiente fragmento:

var tareas = Collections.synchronizedList(new LinkedList<>());
...
tareas.add("Finalizar estudio módulo sobre concurrencia"); // Operación sincronizada

Observa cómo al invocar al método synchronizedList() se entrega como parámetro una lista doblemente enlazada que creamos en ese mismo momento. De igual manera podríamos crear otro tipo de lista, conjunto o diccionario. Todas las operaciones de acceso se efectúan indirectamente a través de los métodos de la clase SynchronizedList, que pasa inadvertida.

Nota: esta última afirmación tiene una excepción: la enumeración de los elementos. Al enumerar una colección, es responsabilidad nuestra incluir el código en un bloque synchronized, de forma que no puedan modificarse mientras se recorre su contenido. El esquema es el indicado en la propia documentación de estas clases, mostrada en la anterior figura de jshell. En ella se aprecia un ejemplo de cómo recorrer sin problemas la lista.

Colecciones thread-safe sin sincronización

La sincronización de todas las operaciones de acceso a una colección, en especial la enumeración de sus elementos, por el tiempo que puede mantener bloqueado ese acceso para el resto de hilos, es una opción que suele afectar de forma importante al rendimiento de las aplicaciones. Dado que, en la mayoría de los casos, los accesos para lectura y recorrido de una colección suelen ser mucho más frecuentes que las operaciones de modificación, una alternativa a las colecciones sincronizadas son las representadas por las clases CopyOnWriteArrayList y CopyOnWriteArraySet.

La peculiaridad de estas colecciones estriba en que no hay ningún mecanismo de sincronización. En su lugar, las operaciones que modifican la colección generan una nueva copia interna de la misma, mientras la antigua se mantiene para cualquier operación de lectura que estuviese en curso. Esto significa que es posible recorrer los elementos de una lista mientras se efectúan cambios en esta, si bien dichos cambios no serían visibles hasta un acceso posterior.

Nota: CopyOnWriteArrayList y CopyOnWriteArraySet usan un ReentrantLock para controlar la solicitud de cambios sobre el contenido de la colección, garantizando así un correcto funcionamiento en aplicaciones con múltiples hilos de ejecución que modifican simultáneamente el contenido.

Para comprobar el funcionamiento de las clases antes citadas, ni siquiera necesitamos tener varios hilos de ejecución, basta con que intentemos cambiar el contenido de la colección mientras estamos enumerándola, tal y como se aprecia en el fragmento de código de la parte superior de la siguiente imagen:

Excepción al modificar la lista

En la parte inferior de la imagen anterior, aparece la excepción que ha generado el programa al ser ejecutado. En este caso valores2 es un ArrayList. No tenemos más que cambiarlo por un CopyOnWriteArrayList y comprobaremos que la operación se permite y funciona como sería de esperar.

Colecciones con acceso concurrente sin bloqueos

Además de las ya descritas, en java.util.concurrent encontramos varias clases más de colección cuya denominación sigue el patrón ConcurrentXXX, por ejemplo ConcurrentLinkedQueue. Se caracterizan porque contemplan el acceso concurrente a los elementos de la colección, incluso para operaciones de modificación, pero no recurren a elementos de sincronización como la palabra clave synchronized o los cerrojos de tipo ReentrantLock.

Los microprocesadores modernos cuentan con instrucciones que permiten comparar dos datos y asignar a uno de ellos un valor según el resultado de dicha comparación, todo ello de manera atómica, en una sola operación. El rendimiento es muy superior al que ofrecería el bloqueo de acceso al dato para su comparación y, si es preciso, posterior asignación. Clases como la citada ConcurrentLinkedQueue se apoyan en esas instrucciones para ir enlazando los nodos de la colección a medida que se agregan o eliminan en un entorno multihilo, sin precisar mecanismos de sincronización.

Además de ConcurrentLinkedQueue y ConcurrentLinkedDeque, también nos serán útiles en entornos multihilo la clase ConcurrentHashMap. Esta nos permite trabajar con un diccionario accesible simultáneamente desde varios hilos de ejecución, efectuando operaciones de lectura y modificación de forma concurrente.

Francisco Charte Francisco lleva más de 30 años dedicado a la enseñanza en informática, tanto en centros privados y públicos como a través de sus libros y artículos. Autor de más de 120 libros y varios centenares de artículos en revistas nacionales e internacionales. Charte es Doctor en Tecnologías de la información y la comunicación por la Universidad de Granada. Puedes seguirlo en Twitter en @fcharte Ver todos los posts de Francisco Charte
Archivado en: Lenguajes y plataformas

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.