¿Cómo puedo hacer que mi ArrayList sea seguro para subprocesos? ¿Otro enfoque al problema en Java?

90

Tengo una ArrayList que quiero usar para contener objetos RaceCar que extienden la clase Thread tan pronto como terminan de ejecutarse. Una clase, llamada Race, maneja esta ArrayList usando un método de devolución de llamada que el objeto RaceCar llama cuando termina de ejecutarse. El método de devolución de llamada, addFinisher (RaceCar finisher), agrega el objeto RaceCar a ArrayList. Se supone que esto da el orden en que los subprocesos terminan de ejecutarse.

Sé que ArrayList no está sincronizado y, por lo tanto, no es seguro para subprocesos. Intenté usar el método Collections.synchronizedCollection (c Collection) pasando una nueva ArrayList y asignando la colección devuelta a una ArrayList. Sin embargo, esto me da un error de compilación:

Race.java:41: incompatible types
found   : java.util.Collection
required: java.util.ArrayList
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

Aquí está el código relevante:

public class Race implements RaceListener {
    private Thread[] racers;
    private ArrayList finishingOrder;

    //Make an ArrayList to hold RaceCar objects to determine winners
    finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

    //Fill array with RaceCar objects
    for(int i=0; i<numberOfRaceCars; i++) {
    racers[i] = new RaceCar(laps, inputs[i]);

        //Add this as a RaceListener to each RaceCar
        ((RaceCar) racers[i]).addRaceListener(this);
    }

    //Implement the one method in the RaceListener interface
    public void addFinisher(RaceCar finisher) {
        finishingOrder.add(finisher);
    }

Lo que necesito saber es, ¿estoy usando un enfoque correcto y, de no ser así, qué debo usar para que mi código sea seguro para subprocesos? ¡Gracias por la ayuda!

ericso
fuente
2
(Nota, la Listinterfaz no es lo suficientemente completa para ser muy útil en múltiples hilos.)
Tom Hawtin - tackline
3
Solo me gustaría señalar que, sin Collections.synchronizedList(), tendríamos una condición de carrera REAL aquí: P
Dylan Watson
Consulte este enlace programmerzdojo.com/java-tutorials/…
rishi007bansod

Respuestas:

147

Utilice Collections.synchronizedList().

Ex:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())
Amir Afghani
fuente
2
¡Gracias! No estoy seguro de por qué no pensé en usar un Vector, ya que recuerdo haber leído en algún lugar donde estaban sincronizados.
ericso
32
Tal vez no sea una buena idea trabajar con clases definidas como obsoletas
frandevel
1
Aunque Vector es bastante antiguo y carece de soporte para colecciones, no está obsoleto. Probablemente sea mejor usar Collections.synchronizedList () como otras personas dijeron aquí.
Asturio
14
-1 para comentarios. Vector no está obsoleto y ¿cómo no tiene soporte para colecciones? Implementa List. El javadoc para Vector dice específicamente: "A partir de la plataforma Java 2 v1.2, esta clase se actualizó para implementar la interfaz List, convirtiéndola en miembro de Java Collections Framework. A diferencia de las nuevas implementaciones de colecciones, Vector está sincronizado". Puede haber buenas razones para no usar Vector (evitar la sincronización, cambiar implementaciones), pero ser "obsoleto" o "no moderno" no es una de ellas.
fool4jesus
1
Utilice los siguientes métodos: Collections.synchronizedList (lista); Collections.synchronizedSet (conjunto); Collections.synchronizedMap (mapa); Los métodos anteriores toman la colección como parámetro y devuelven el mismo tipo de colección que está sincronizada y es segura para subprocesos.
Sameer Kazi
35

Cambio

private ArrayList finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars)

a

private List finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedList(new ArrayList(numberOfRaceCars)

List es un supertipo de ArrayList, por lo que debe especificarlo.

De lo contrario, lo que estás haciendo parece estar bien. Otra opción es que puede usar Vector, que está sincronizado, pero esto es probablemente lo que haría yo.

Reverendo Gonzo
fuente
1
O Listprobablemente sería más útil. O List<RaceCar>.
Tom Hawtin - tackline
Buen punto, hacen que sea privada Lista finishingOrder = Collections.synchronizedList (...)
Reverendo Gonzo
Intenté esto y el compilador ahora se queja de que llamo a los métodos ArrayList en una colección: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); dice que no se encuentra el método get (0). Pensamientos
ericso
Perdón por eliminar y volver a agregar mi comentario. Estaba tratando de hacer que el resaltado funcionara usando comillas invertidas. Tengo TOC por ese tipo de cosas.
ericso
No, eso no funciona. No convertirá la colección en una lista: Race.java:41: tipos incompatibles encontrados: java.util.Collection required: java.util.List finishOrder = Collections.synchronizedCollection (new ArrayList (numberOfRaceCars));
ericso
11

CopyOnWriteArrayList

Usa CopyOnWriteArrayListclase. Esta es la versión segura para subprocesos de ArrayList.

Singh Piyush
fuente
3
Piense dos veces al considerar esta clase. Para citar el documento de la clase: "Esto normalmente es demasiado costoso, pero puede ser más eficiente que las alternativas cuando las operaciones transversales superan en número a las mutaciones, y es útil cuando no puede o no desea sincronizar recorridos, pero necesita evitar la interferencia entre subprocesos concurrentes. . " Además, consulte Diferencia entre CopyOnWriteArrayList y synchronizedList
Basil Bourque
esta clase entra en juego cuando rara vez modifica la lista, pero a menudo itera sobre los elementos. por ejemplo, cuando tiene un grupo de oyentes. los registra y luego itera mucho ..., si no necesita explícitamente la interfaz de lista, pero modifica y lee operaciones para que sean concurrentes, considereConcurrentLinkedQueue
benez
7

Es posible que esté utilizando el enfoque incorrecto. El hecho de que un hilo que simula un coche termine antes que otro hilo de simulación de coche no significa que el primer hilo deba ganar la carrera simulada.

Depende mucho de tu aplicación, pero podría ser mejor tener un hilo que calcule el estado de todos los autos en pequeños intervalos de tiempo hasta que se complete la carrera. O, si prefiere utilizar varios hilos, puede hacer que cada automóvil registre el tiempo "simulado" que tardó en completar la carrera y elegir al ganador como el que tiene el menor tiempo.

erickson
fuente
Ese es un buen punto. Este es solo un ejercicio de un texto que estoy usando para aprender Java. El objetivo era aprender a usar los hilos y, de hecho, voy más allá de las especificaciones originales del problema al crear un mecanismo para registrar los ganadores. Pensé en usar un temporizador para medir a los ganadores. Pero, sinceramente, creo que he obtenido lo que necesito del ejercicio.
ericso
5

También puede usar la synchronizedpalabra clave para un addFinishermétodo como este

    //Implement the one method in the RaceListener interface
    public synchronized void addFinisher(RaceCar finisher) {
        finishingOrder.add(finisher);
    }

Por lo tanto, puede usar ArrayList agregar método seguro para subprocesos de esta manera.

erhun
fuente
4
bueno, pero ¿qué pasa si tienes dos métodos: addFinisher y delFinisher? Ambos métodos son seguros para subprocesos, pero dado que ambos acceden a la misma ArrayList, aún tendría problemas.
omni
1
@masi Luego, simplemente sincroniza en un final Objectlugar cada vez que acceda Collectiona él de cualquier manera.
mkuech
2

Siempre que desee utilizar una versión segura para subprocesos ant del objeto de colección ant, utilice la ayuda del paquete java.util.concurrent. * . Tiene casi todas las versiones simultáneas de objetos de colección no sincronizados. por ejemplo: para ArrayList, tiene java.util.concurrent.CopyOnWriteArrayList

Puede hacer Collections.synchronizedCollection (cualquier objeto de colección), pero recuerde este sincronizador clásico. La técnica es cara y viene con gastos generales de ejecución. El paquete java.util.concurrent. * es menos costoso y administra el rendimiento de una mejor manera mediante el uso de mecanismos como

copiar al escribir, comparar e intercambiar, bloquear, iteradores de instantáneas, etc.

Por lo tanto, prefiera algo del paquete java.util.concurrent. *

Jaydeep Ramesh Deshmukh
fuente
1

En su lugar, también puede usar como Vector, ya que los vectores son seguros para subprocesos y la lista de matrices no. Aunque los vectores son antiguos, pueden resolver su propósito fácilmente.

Pero puede hacer que su Arraylist esté sincronizada como un código dado esto:

Collections.synchronizedList(new ArrayList(numberOfRaceCars())); 
Naman jain
fuente
-1

Puede cambiar de tipo ArrayList a Vector, en el que todos los métodos están sincronizados.

private Vector finishingOrder;
//Make a Vector to hold RaceCar objects to determine winners
finishingOrder = new Vector(numberOfRaceCars);
Darlinton
fuente
5
Si va a sugerir el uso de otra colección, probablemente Vector sea una mala elección. Es una colección heredada que se adaptó al diseño del nuevo marco de colecciones de Java. Estoy seguro de que hay mejores opciones en el paquete java.until.concurrent.
Edwin Dalorzo