2009-02-18 8 views
24

¿Debo usar una colección de vectores sincronizados anterior, ArrayList con acceso sincronizado o Collections.synchronizedList o alguna otra solución para el acceso concurrente?La mejor forma de controlar el acceso simultáneo a las colecciones de Java

No veo mi pregunta en Preguntas relacionadas ni en mi búsqueda (Make your collections thread-safe? no es lo mismo).

Recientemente, tuve que realizar pruebas de unidad de tipo en partes de la GUI de nuestra aplicación (básicamente usando API para crear marcos, agregar objetos, etc.). Debido a que estas operaciones son llamadas mucho más rápido que por un usuario, muestra una serie de problemas con los métodos que intentan acceder a los recursos que aún no se han creado o que ya no se eliminaron.

Un problema particular, sucediendo en el EDT, vino de recorrer una lista vinculada de vistas mientras la alteraba en otro hilo (obteniendo una ConcurrentModificationException entre otros problemas). No me preguntes por qué era una lista vinculada en lugar de una simple lista de arreglos (incluso menos que en general tenemos 0 o 1 vista dentro ...), así que tomé la ArrayList más común en mi pregunta (ya que un primo mayor).

De todos modos, no muy familiarizado con problemas de concurrencia, busqué un poco de información, y me pregunté qué elegir entre el Vector viejo (y probablemente obsoleto) (que tiene operaciones sincronizadas por diseño), ArrayList con un synchronized (myList) { } alrededor de crítico secciones (operaciones de agregar/eliminar/caminar) o usar una lista devuelta por Collections.synchronizedList (ni siquiera está seguro de cómo usar esta última).

Finalmente elegí la segunda opción, porque otro error de diseño fue exponer el objeto (método getViewList() ...) en lugar de proporcionar mecanismos para usarlo.

Pero, ¿cuáles son los pros y los contras de los otros enfoques?


[EDITAR] Muchos buenos consejos aquí, difíciles de seleccionar. Elegiré los más detallados y proporcionaré enlaces/alimentos para pensamientos ... :-) También me gusta el de Darron.

En resumen:

  • Como sospechaba, Vector (y su gemelo malvado, Hashtable así, probablemente) es en gran medida obsoleta, he visto a la gente diciendo su antiguo diseño no es tan bueno como las nuevas colecciones ', más allá de la lentitud de la sincronización obligada incluso en el entorno de subproceso único. Si lo mantenemos, es principalmente porque las bibliotecas antiguas (y partes de la API de Java) aún lo usan.
  • A diferencia de lo que pensaba, Collections.synchronizedXxxx no son más modernos que Vector (parecen ser contemporáneos a Collections, es decir, Java 1.2!) Y en realidad no son mejores. Bueno saber. En resumen, debería evitarlos también.
  • La sincronización manual parece ser una buena solución después de todo. Puede haber problemas de rendimiento, pero en mi caso no es crítico: operaciones realizadas en acciones del usuario, colección pequeña, sin uso frecuente.
  • El paquete java.util.concurrent vale la pena tenerlo en cuenta, en particular los métodos CopyOnWrite.

espero que lo hizo bien ... :-)

+0

También vea http://stackoverflow.com/questions/510632/whats-the-difference-between-concurrenthashmap-and-collections-synchronizedmapm – eljenso

Respuesta

23

Vector y la Lista devuelta por Collections.synchronizedList() son moralmente la misma cosa. Consideraría que Vector se ha depreciado efectivamente (pero no en realidad) y siempre prefiero una Lista sincronizada. La única excepción serían las antiguas API (especialmente las que están en el JDK) que requieren un Vector.

Usar una ArrayList desnuda y sincronizar de manera independiente le da la oportunidad de sintonizar su sincronización con mayor precisión (ya sea mediante acciones adicionales en el bloque mutuamente excluyente o reuniendo múltiples llamadas a la Lista en una acción atómica). El inconveniente es que es posible escribir código que accede a la ArrayList desnuda fuera de sincronización, que está rota.

Otra opción que quizás desee considerar es una CopyOnWriteArrayList, que le dará seguridad de subprocesos como Vector y ArrayList sincronizado, pero también iteradores que no lanzarán ConcurrentModificationException ya que están trabajando en una instantánea no activa de los datos.

Usted puede encontrar algunos de estos últimos blogs sobre estos temas interesantes:

3

No creo que la Iterator devuelto por un vector es de ninguna manera sincronizada - lo que significa que no puede garantizar Vector (en su propio) que un hilo no está modificando la colección subyacente mientras otro hilo está iterando a través de él.

Creo que para garantizar que la iteración sea segura para subprocesos, tendrá que manejar la sincronización usted mismo. Suponiendo que el mismo Vector/Lista/objeto es compartido por múltiples hilos (y por lo tanto conduce a sus problemas), ¿no puede simplemente sincronizar en ese objeto?

10

No puedo pensar en una buena razón para alguna vez preferir Vector sobre ArrayList. Las operaciones de lista en un Vector están sincronizadas, lo que significa que varios hilos pueden alterarla de forma segura. Y como dices, las operaciones de ArrayList se pueden sincronizar usando Collections.synchronizedList.

Recuerde que incluso cuando usa listas sincronizadas, aún encontrará ConcurrentModificationExceptions si itera sobre la colección mientras está siendo modificada por otra cadena. Entonces, es importante coordinar ese acceso externamente (su segunda opción).

Una técnica común utilizada para evitar el problema de iteración consiste en iterar sobre copias inmutables de la colección. Ver Collections.unmodifiableList

7

siempre iría primero al paquete java.util.concurrent (http://java.sun.com/javase/6/docs/api/java/util/concurrent/package-summary.html) y vería si hay alguna colección adecuada. la clase java.util.concurrent.CopyOnWriteArrayList es buena si realiza pocos cambios en la lista pero más iteraciones.

Además, no creo que Vector and Collections.synchronizedList evite la excepción ConcurrentModificationException.

si no encuentra una colección adecuada, entonces tendría que hacer su propia sincronización, y si no desea mantener un bloqueo al iterar, puede considerar hacer una copia e iterar la copia.

2

La solución más segura es evitar el acceso simultáneo a datos compartidos. En lugar de tener subprocesos no EDT que funcionen con los mismos datos, pídales que llamen al SwingUtilities.invokeLater() con un Runnable que realice las modificaciones.

En serio, la concurrencia de datos compartidos es un nido de víboras en el que nunca sabrás si no hay otra condición de carrera o punto muerto escondido en algún lado, esperando que te muerda el culo en la peor ocasión posible.

2

Vale la pena mirar CopyOnWriteArrayList. Está diseñado para una lista que generalmente se lee. Cada escritura provocaría la creación de una nueva matriz detrás de las cubiertas, por lo que las iteraciones en la matriz no obtendrían una ConcurrentModificationException

13

os recomiendo el libro "Java Concurrency in Practice".

Cada una de las opciones tiene ventajas/desventajas:

  1. vectoriales - considera "obsoleta". Puede recibir menos atención y correcciones de errores que otras colecciones convencionales.
  2. Sus propios bloques de sincronización - Muy fácil de obtener incorrecto. A menudo da un rendimiento más pobre que las opciones a continuación.
  3. Collections.synchronizedList() - Elección 2 realizada por expertos. Esto aún no está completo, debido a las operaciones de varios pasos que deben ser atómicas (get/modify/set o iteration).
  4. Nuevas clases de java.util.concurrent - A menudo tienen algoritmos más eficientes que la opción 3. Se aplican advertencias similares sobre las operaciones de varios pasos pero a menudo se proporcionan herramientas para ayudarlo.
Cuestiones relacionadas