Por favor, activa JavaScript y desactiva tu adblock para este sitio

El Javatar

Blog dedicado a la Programación en Java, C, PHP, Phyton, HTML, SQL y Mucho Más

viernes, 12 de enero de 2018

¿Cómo Sincronizar un ArrayList en Java?

Cómo Sincronizar un ArrayList en Java
ArrayList es una Colección muy útil en Java, e incluso es una de la más usadas, aunque no es una colección sincronizada. ¿Qué significa esto? Significa que no se puede compartir una instancia de un ArrayList entre varios hilos (threads) no sólo para operaciones de lectura, sino tampoco para agregar o actualizar elementos.

Entonces, ¿Cómo podemos sincronizar un ArrayList en Java? Eso es lo que trataremos de explicar y con ejemplos en este tutorial, pero primero veamos la razón del por qué los ArrayList no están sincronizados por defecto en el API de Java. Los multi-procesos o multi-threading son una de las principales fortalezas de Java, e incluso casi todos los programas desarrollados en Java tienen más de un hilo; entonces ¿por qué Java no nos facilita de forma nativa el uso de ArrayList para dicho entorno?

La respuesta está en el rendimiento; hay un costo de rendimiento asociado con la sincronización y hacer que un ArrayList esté sincronizado haría que fuese más lento. Por lo tanto, los ArrayList quedaron nativamente en el API de Java como colecciones no sincronizadas para mantenerlos rápidos; sin embargo, el API de Java también nos proporciona formas fáciles de sincronizar un ArrayList en caso de que lo necesitemos, y esto es lo que vamos a aprender en este tutorial.

La clase Collections del API de Java tiene varios métodos para crear List, Set y Map sincronizados, y para este tutorial utilizaremos el método Collections.synchronizedList() para sincronizar un ArrayList. Este método acepta una lista que puede ser cualquier implementación de la interface List, como por ejemplo, ArrayList y LinkedList, y lo que nos devuelve es una lista sincronizada thread-safe (segura para subprocesos) respaldada por la lista especificada.

Lista Sincronizada e Iteración en Java


Uno de los principales desafíos a los que nos enfrentamos cuando queremos compartir un ArrayList entre múltiples hilos es el lidiar con situaciones en las que un hilo intenta acceder a un elemento que otro hilo ha eliminado. Si se utilizan métodos como get(index) o remove(index) para obtener o eliminar elementos, también es posible que otro hilo también esté intentando eliminar dichos elementos, aunque de forma no fiable, a menos de que se realice una verificación manual de la lista antes de usar los métodos get(index) y remove(index), lo cual significa que no se puede llamar a estos métodos de forma fiable sin antes verificar el tamaño de la lista, para lo cual también debe proporcionarse una sincronización adicional entre la llamada de los métodos size() y remove(int index).

Para garantizar el acceso en serie, es fundamental que todo el acceso a la lista de respaldo se realice a través de la lista devuelta y es imperativo que el usuario realice una sincronización manual en la lista devuelta al iterar sobre ella como se muestra en el siguiente código de ejemplo:

List list = Collections.synchronizedList(new ArrayList());

...
synchronized(list) {
    Iterator itr = list.iterator(); // El iterador debe estar en un bloque sincronizado
    while (itr.hasNext()) {
        Object obj = itr.next();
    }
}

Como se sugiere en la documentación de Java, no seguir este consejo puede dar como resultado un comportamiento no determinista, es decir, que no podemos saber de antemano cuál será el resultado cuando llamemos a los métodos get(index) y remove(index). Además, la lista será serializable si la ArrayList proporcionado es serializable.

Código para Sincronizar un ArrayList en Java

A continuación veremos un ejemplo completo para sincronizar un ArrayList en Java. Lo que haremos será crear una lista de String y añadiremos algunos elementos en ella. Luego,  pasaremos dicha lista al método Collections.synchronizedList(), el cual nos devolverá una versión thread-safe (segura para subprocesos) de la lista.

Con esto, ya podríamos compartir esta lista de forma segura entre varios threads, pero debemos tener cuidado al recuperar o eliminar elementos del ArrayList. Así que para tener acceso seguro, debemos de usar un Iterator de forma sincronizada para obtener y eliminar elementos de la lista, tal como lo veremos en este ejemplo:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


/**
 * How to synchronize ArrayList in Java
 * 
 * @author Andrés Mauricio (http://www.eljavatar.com)
 */

public class ArrayListSincronizadoDemo {

    public static void main(String args[]) {
       
       // Creamos una lista, la cual no está sincronizada
       List<String> marcasCelulares = new ArrayList<String>();
       
       marcasCelulares.add("SAMSUNG");
       marcasCelulares.add("IPHONE");
       marcasCelulares.add("HUAWEI");
       marcasCelulares.add("MOTOROLA");
       marcasCelulares.add("HTC");
       
       // Sincronizamos la lista
       marcasCelulares = Collections.synchronizedList(marcasCelulares);
       
       // Mientras iteramos sobre la lista sincronizada, se debe
       // sincronizar el iterador y las operaciones para evitar
       // comportamientos no deterministas
       
       synchronized(marcasCelulares) {
           Iterator<String> itr = marcasCelulares.iterator();
           
           while (itr.hasNext()) {
               System.out.println(itr.next());
           }
       }
       
    }

}

Salida de nuestro programa:
SAMSUNG
IPHONE
HUAWEI
MOTOROLA
HTC

Como hemos visto, es muy fácil sincronizar un ArrayList en Java gracias al método Collections.synchronizedList(), sin embargo, debemos ser un poco cuidadosos al recuperar y eliminar objetos de la Lista. Por cierto, existen otras dos opciones para conseguir una lista de elementos sincronizada, las cuales son Vector y CopyOnWriteArrayList.

Cómo Sincronizar un ArrayList en Java

Vector es una clase muy antigua, pero fue adaptada para implementar la interface List a partir de Java 1.4 y, lo que es más importante, está sincronizada, por lo cual no es necesario sincronizarla de nuevo. Por cierto, debes tener en cuenta que al usar la clase Vector, asegúrate de usarlo mediante la interface List y no mediante el uso de métodos heredados; de lo contrario, en caso de que desees cambiar de implementación después, no podrás hacerlo.

Por otro lado, CopyOnWriteArrayList es parte de las clases de colecciones concurrentes de Java y es mucho más escalable que Vector y ArrayList. Si una lista va a ser usada principalmente para leer registros, y sólo de vez en cuando va ser usada para realizar en ella operaciones de escritura, esta podría ser la elección más idónea.

No hay comentarios.:

Publicar un comentario