Wie mache ich meine ArrayList Thread-sicher? Ein anderer Ansatz für Probleme in Java?


89

Ich habe eine ArrayList, mit der ich RaceCar-Objekte speichern möchte, die die Thread-Klasse erweitern, sobald die Ausführung abgeschlossen ist. Eine Klasse namens Race verarbeitet diese ArrayList mithilfe einer Rückrufmethode, die das RaceCar-Objekt nach Abschluss der Ausführung aufruft. Die Rückrufmethode addFinisher (RaceCar-Finisher) fügt das RaceCar-Objekt zur ArrayList hinzu. Dies soll die Reihenfolge angeben, in der die Ausführung der Threads beendet ist.

Ich weiß, dass ArrayList nicht synchronisiert und daher nicht threadsicher ist. Ich habe versucht, die Methode Collections.synchronizedCollection (c Collection) zu verwenden, indem ich eine neue ArrayList übergeben und die zurückgegebene Collection einer ArrayList zugewiesen habe. Dies gibt mir jedoch einen Compilerfehler:

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

Hier ist der relevante Code:

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

Was ich wissen muss ist, verwende ich einen korrekten Ansatz und wenn nicht, was sollte ich verwenden, um meinen Code threadsicher zu machen? Danke für die Hilfe!


2
(Beachten Sie, dass die ListBenutzeroberfläche nicht vollständig genug ist, um beim Multithreading sehr nützlich zu sein.)
Tom Hawtin - Tackline

3
Ich möchte nur darauf hinweisen, dass Collections.synchronizedList()wir ohne hier eine WIRKLICHE Rennbedingung hätten: P
Dylan Watson

Antworten:


144

Verwenden Collections.synchronizedList() .

Ex:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())

2
Vielen Dank! Ich bin mir nicht sicher, warum ich nicht daran gedacht habe, nur einen Vektor zu verwenden, da ich mich daran erinnere, dass ich irgendwo gelesen habe, dass sie synchronisiert wurden.
Erico

32
Vielleicht ist es keine gute Idee, mit Klassen zu arbeiten, die als veraltet definiert sind
frandevel

1
Obwohl Vector ziemlich alt ist und keine Sammlungsunterstützung bietet, ist es nicht veraltet. Es ist wahrscheinlich besser, Collections.synchronizedList () zu verwenden, wie andere hier gesagt haben.
Asturio

14
-1 für Kommentare. Vektor ist nicht veraltet und wie unterstützt er keine Sammlungen? Es implementiert List. Im Javadoc für Vector heißt es ausdrücklich: "Ab der Java 2-Plattform v1.2 wurde diese Klasse nachgerüstet, um die List-Schnittstelle zu implementieren, sodass sie Mitglied des Java Collections Framework ist. Im Gegensatz zu den neuen Collection-Implementierungen ist Vector synchronisiert." Es kann gute Gründe geben, Vector nicht zu verwenden (Synchronisation vermeiden, Implementierungen austauschen), aber "veraltet" oder "nicht modern" zu sein, gehört nicht dazu.
narr4jesus

1
Verwenden Sie die folgenden Methoden: Collections.synchronizedList (Liste); Collections.synchronizedSet (set); Collections.synchronizedMap (Karte); Die oben genannten Methoden verwenden die Sammlung als Parameter und geben denselben Sammlungstyp zurück, der synchronisiert und threadsicher ist.
Sameer Kazi

35

Veränderung

private ArrayList finishingOrder;

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

zu

private List finishingOrder;

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

List ist ein Supertyp von ArrayList, daher müssen Sie dies angeben.

Ansonsten scheint das, was Sie tun, in Ordnung zu sein. Eine andere Option ist, dass Sie Vector verwenden können, der synchronisiert ist, aber dies ist wahrscheinlich das, was ich tun würde.


1
Oder Listwäre wahrscheinlich nützlicher. Oder List<RaceCar>.
Tom Hawtin - Tackline

Guter Punkt, machen Sie es privat Liste finishingOrder = Collections.synchronizedList (...)
Reverend Gonzo

Ich habe es versucht und der Compiler beschwert sich jetzt darüber, dass ich ArrayList-Methoden für eine Sammlung aufrufe: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); Es heißt, dass die Methode get (0) nicht gefunden wurde. Gedanken?
Erico

Es tut mir leid, dass ich meinen Kommentar gelöscht und wieder hinzugefügt habe. Ich habe versucht, die Hervorhebung mithilfe von Backticks zum Laufen zu bringen. Ich bekomme OCD über solche Sachen.
Erico

Nein, das funktioniert nicht. Die Sammlung wird nicht in eine Liste umgewandelt: Race.java:41: Inkompatible Typen gefunden: java.util.Collection erforderlich: java.util.List finishOrder = Collections.synchronizedCollection (neue ArrayList (numberOfRaceCars));
Erico

11

CopyOnWriteArrayList

Verwenden CopyOnWriteArrayListKlasse. Dies ist die thread-sichere Version von ArrayList.


3
Überlegen Sie zweimal, wenn Sie über diese Klasse nachdenken. Um das Klassendokument zu zitieren: „Dies ist normalerweise zu kostspielig, kann jedoch effizienter sein als Alternativen, wenn Traversal-Operationen die Anzahl der Mutationen bei weitem übersteigen, und ist nützlich, wenn Sie Traversals nicht synchronisieren können oder wollen, aber Interferenzen zwischen gleichzeitigen Threads ausschließen müssen . ” Siehe auch Unterschied zwischen CopyOnWriteArrayList und synchronizedList
Basil Bourque

Diese Klasse kommt ins Spiel, wenn Sie die Liste selten ändern, aber häufig über die Elemente iterieren. zB wenn Sie eine Reihe von Zuhörern haben. Sie registrieren sie und iterieren dann viel ... Wenn Sie die ConcurrentLinkedQueue
Listenschnittstelle

7

Sie könnten mit dem falschen Ansatz. Nur weil ein Thread, der ein Auto simuliert, vor einem anderen Auto-Simulations-Thread endet, bedeutet dies nicht, dass der erste Thread das simulierte Rennen gewinnen sollte.

Es hängt sehr von Ihrer Anwendung ab, aber es ist möglicherweise besser, einen Thread zu haben, der den Status aller Autos in kleinen Zeitintervallen berechnet, bis das Rennen beendet ist. Wenn Sie mehrere Threads bevorzugen, kann jedes Auto die "simulierte" Zeit aufzeichnen, die für die Beendigung des Rennens benötigt wurde, und den Sieger als den mit der kürzesten Zeit auswählen.


Das ist ein guter Punkt. Dies ist nur eine Übung aus einem Text, mit dem ich Java lerne. Es ging darum zu lernen, wie man Threads verwendet, und ich gehe tatsächlich über die ursprünglichen Spezifikationen des Problems hinaus, um einen Mechanismus zum Protokollieren der Gewinner zu erstellen. Ich dachte darüber nach, einen Timer zu verwenden, um die Gewinner zu messen. Aber ehrlich gesagt denke ich, dass ich durch die Übung das bekommen habe, was ich brauche.
Erico

5

Sie können auch ein synchronizedSchlüsselwort für eine solche addFinisherMethode verwenden

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

Auf diese Weise können Sie die ArrayList add-Methode threadsicher verwenden.


4
Nun, aber was ist, wenn Sie zwei Methoden haben: addFinisher und delFinisher? Beide Methoden sind threadsicher, aber da beide auf dieselbe ArrayList zugreifen, treten immer noch Probleme auf.
Masi

1
@masi Dann synchronisierst du final Objectstattdessen einfach jedes Mal, wenn du auf Collectionirgendeine Weise auf das zugreifst .
Mkuech

2

Wenn Sie eine Ant-Thread-sichere Version des Ant-Collection-Objekts verwenden möchten, verwenden Sie das Paket java.util.concurrent. * . Es gibt fast alle gleichzeitigen Versionen von nicht synchronisierten Sammlungsobjekten. Beispiel: Für ArrayList haben Sie java.util.concurrent.CopyOnWriteArrayList

Sie können Collections.synchronizedCollection (jedes Sammlungsobjekt) ausführen, aber denken Sie an diese klassische Synchronisation. Technik ist teuer und mit Leistungsaufwand verbunden. Das Paket java.util.concurrent. * ist kostengünstiger und verwaltet die Leistung mithilfe von Mechanismen wie

Copy-on-Write, Compare-and-Swap, Lock, Snapshot-Iteratoren usw.

Bevorzugen Sie also etwas aus dem Paket java.util.concurrent. *


1

Sie können stattdessen auch als Vektor verwenden, da Vektoren threadsicher sind und Arraylist nicht. Obwohl Vektoren alt sind, können sie Ihren Zweck leicht lösen.

Sie können Ihre Arraylist jedoch wie folgt synchronisieren:

Collections.synchronizedList(new ArrayList(numberOfRaceCars())); 

-1

Sie können von ArrayList zu Vektortyp wechseln, in dem jede Methode synchronisiert wird.

private Vector finishingOrder;
//Make a Vector to hold RaceCar objects to determine winners
finishingOrder = new Vector(numberOfRaceCars);

5
Wenn Sie vorschlagen, eine andere Sammlung zu verwenden, ist Vector wahrscheinlich eine schlechte Wahl. Es handelt sich um eine Legacy-Sammlung, die für das Design des neuen Java Collections Framework nachgerüstet wurde. Ich bin sicher, dass das Paket java.until.concurrent bessere Auswahlmöglichkeiten bietet.
Edwin Dalorzo
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.