Comment rendre mon ArrayList Thread-Safe? Une autre approche du problème en Java?

90

J'ai une ArrayList que je veux utiliser pour contenir des objets RaceCar qui étendent la classe Thread dès qu'ils ont fini de s'exécuter. Une classe, appelée Race, gère cette ArrayList à l'aide d'une méthode de rappel que l'objet RaceCar appelle une fois son exécution terminée. La méthode de rappel, addFinisher (finisseur RaceCar), ajoute l'objet RaceCar à ArrayList. Ceci est censé donner l'ordre dans lequel les threads finissent de s'exécuter.

Je sais que ArrayList n'est pas synchronisé et n'est donc pas thread-safe. J'ai essayé d'utiliser la méthode Collections.synchronizedCollection (c Collection) en passant une nouvelle ArrayList et en attribuant la collection retournée à une ArrayList. Cependant, cela me donne une erreur de compilation:

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

Voici le code pertinent:

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);
    }

Ce que j'ai besoin de savoir, c'est si j'utilise une approche correcte et si non, que dois-je utiliser pour rendre mon code thread-safe? Merci pour l'aide!

Ericso
la source
2
(Notez que l' Listinterface n'est pas vraiment assez complète pour être très utile en multithreading.)
Tom Hawtin - tackline
3
Je voudrais juste souligner que, sans Collections.synchronizedList(), nous aurions une VRAIE condition de course ici: P
Dylan Watson
Vérifiez ce lien programmerzdojo.com/java-tutorials
...

Réponses:

145

Utilisez Collections.synchronizedList().

Ex:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())
Amir Afghani
la source
2
Merci! Je ne sais pas pourquoi je n'ai pas pensé à utiliser simplement un vecteur puisque je me souviens avoir lu quelque part, ils étaient synchronisés.
ericso
32
Ce n'est peut-être pas une bonne idée de travailler avec des classes définies comme obsolètes
frandevel
1
Bien que Vector soit assez ancien et ne supporte pas les collections, il n'est pas obsolète. Il est probablement préférable d'utiliser Collections.synchronizedList () comme d'autres personnes l'ont dit ici.
Asturio le
14
-1 pour les commentaires. Vector n'est pas obsolète et comment ne prend-il pas en charge les collections? Il implémente List. Le javadoc pour Vector dit spécifiquement: «À partir de la plate-forme Java 2 v1.2, cette classe a été modernisée pour implémenter l'interface List, ce qui en fait un membre du Java Collections Framework. Contrairement aux nouvelles implémentations de collection, Vector est synchronisé. Il peut y avoir de bonnes raisons de ne pas utiliser Vector (éviter la synchronisation, changer les implémentations), mais être "obsolète" ou "pas moderne" n'en fait pas partie.
fool4jesus
1
Utilisez les méthodes ci-dessous: Collections.synchronizedList (list); Collections.synchronizedSet (ensemble); Collections.synchronizedMap (carte); Les méthodes ci-dessus prennent la collection comme paramètre et renvoient le même type de collection qui sont synchronisées et thread-safe.
Sameer Kazi
35

Changement

private ArrayList finishingOrder;

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

à

private List finishingOrder;

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

List est un supertype de ArrayList, vous devez donc le spécifier.

Sinon, ce que vous faites semble bien. Une autre option est que vous pouvez utiliser Vector, qui est synchronisé, mais c'est probablement ce que je ferais.

Révérend Gonzo
la source
1
Ou Listserait probablement plus utile. Ou List<RaceCar>.
Tom Hawtin - Tacle du
Bon point, rendez-le privé Liste finishOrder = Collections.synchronizedList (...)
Reverend Gonzo
J'ai essayé cela et le compilateur se plaint maintenant de mon appel des méthodes ArrayList sur une collection: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); il dit que la méthode get (0) n'est pas trouvée. Pensées?
ericso
Désolé d'avoir supprimé et rajouté mon commentaire. J'essayais de faire fonctionner la mise en évidence en utilisant des backticks. J'ai un trouble obsessionnel-compulsif à propos de ce genre de choses.
ericso
Non, ça ne marche pas. Il ne convertit pas la collection dans une liste: Race.java:41: types incompatibles trouvés: java.util.Collection requis: java.util.List finishOrder = Collections.synchronizedCollection (new ArrayList (numberOfRaceCars));
ericso
11

CopyOnWriteArrayList

Utilisez la CopyOnWriteArrayListclasse. Il s'agit de la version thread-safe de ArrayList.

Singh Piyush
la source
3
Réfléchissez à deux fois lorsque vous envisagez cette classe. Pour citer la documentation de la classe: «Ceci est généralement trop coûteux, mais peut être plus efficace que les alternatives lorsque les opérations de traversée sont largement plus nombreuses que les mutations, et est utile lorsque vous ne pouvez pas ou ne voulez pas synchroniser les traversées, mais que vous devez empêcher les interférences entre les threads concurrents . » Voir également Différence entre CopyOnWriteArrayList et synchronizedList
Basil Bourque
cette classe entre en jeu lorsque vous modifiez rarement la liste, mais souvent itérez sur les éléments. par exemple, lorsque vous avez un ensemble d'auditeurs. vous les enregistrez et ensuite vous itérez beaucoup ..., si vous n'avez pas explicitement besoin de l'interface de liste, mais que vous modifiez et lisez les opérations pour ConcurrentLinkedQueue
qu'elles
7

Vous utilisez peut- être la mauvaise approche. Ce n'est pas parce qu'un thread qui simule une voiture se termine avant un autre thread de simulation de voiture que le premier thread devrait gagner la course simulée.

Cela dépend beaucoup de votre application, mais il serait peut-être préférable d'avoir un thread qui calcule l'état de toutes les voitures à de petits intervalles de temps jusqu'à ce que la course soit terminée. Ou, si vous préférez utiliser plusieurs threads, vous pouvez demander à chaque voiture d'enregistrer le temps «simulé» nécessaire pour terminer la course et de choisir le vainqueur comme celui avec le temps le plus court.

Erickson
la source
C'est un bon point. Ceci est juste un exercice à partir d'un texte que j'utilise pour apprendre Java. Le but était d'apprendre à utiliser les threads et je vais en fait au-delà des spécifications d'origine du problème en créant un mécanisme pour enregistrer les gagnants. J'ai pensé à utiliser une minuterie pour mesurer les gagnants. Mais honnêtement, je pense avoir obtenu ce dont j'ai besoin grâce à l'exercice.
ericso
5

Vous pouvez également utiliser un synchronizedmot-clé pour une addFinisherméthode comme celle-ci

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

Vous pouvez donc utiliser ArrayList add méthode thread-safe de cette manière.

Erhun
la source
4
eh bien, mais que faire si vous aviez deux méthodes: addFinisher et delFinisher? Les deux méthodes sont thread-safe, mais comme les deux accèdent à la même ArrayList, vous auriez toujours des problèmes.
masi
1
@masi Ensuite, vous synchronisez simplement sur un à la final Objectplace chaque fois que vous accédez au Collectionde quelque manière que ce soit.
mkuech
2

Chaque fois que vous souhaitez utiliser la version ant thread-safe de la collection ant, prenez l'aide du package java.util.concurrent. * . Il a presque toutes les versions simultanées des objets de collection non synchronisés. par exemple: pour ArrayList, vous avez java.util.concurrent.CopyOnWriteArrayList

Vous pouvez faire Collections.synchronizedCollection (n'importe quel objet de collection), mais souvenez-vous de ce synchr classique. La technique est coûteuse et s'accompagne de frais généraux de performance. Le package java.util.concurrent. * est moins cher et gère mieux les performances en utilisant des mécanismes tels que

copie sur écriture, comparaison et échange, verrouillage, itérateurs d'instantanés, etc.

Alors, préférez quelque chose du package java.util.concurrent. *

Jaydeep Ramesh Deshmukh
la source
1

Vous pouvez également utiliser comme vecteur à la place, car les vecteurs sont thread-safe et les arraylist ne le sont pas. Bien que les vecteurs soient anciens, ils peuvent facilement résoudre votre objectif.

Mais vous pouvez synchroniser votre Arraylist comme du code étant donné ceci:

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

Vous pouvez passer du type ArrayList au type Vector, dans lequel chaque méthode est synchronisée.

private Vector finishingOrder;
//Make a Vector to hold RaceCar objects to determine winners
finishingOrder = new Vector(numberOfRaceCars);
Darlinton
la source
5
Si vous envisagez d'utiliser une autre collection, Vector est probablement un mauvais choix. Il s'agit d'une collection héritée qui a été adaptée à la conception du nouveau Java Collections Framework. Je suis sûr qu'il existe de meilleurs choix dans le package java.until.concurrent.
Edwin Dalorzo