Come posso rendere il mio ArrayList thread-safe? Un altro approccio al problema in Java?


90

Ho un ArrayList che desidero utilizzare per contenere oggetti RaceCar che estendono la classe Thread non appena hanno terminato l'esecuzione. Una classe, denominata Race, gestisce questo ArrayList utilizzando un metodo di callback che l'oggetto RaceCar chiama al termine dell'esecuzione. Il metodo di callback, addFinisher (RaceCar finisher), aggiunge l'oggetto RaceCar all'ArrayList. Questo dovrebbe fornire l'ordine in cui i thread terminano l'esecuzione.

So che ArrayList non è sincronizzato e quindi non è thread-safe. Ho provato a utilizzare il metodo Collections.synchronizedCollection (c Collection) passando un nuovo ArrayList e assegnando la Collection restituita a un ArrayList. Tuttavia, questo mi dà un errore del compilatore:

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

Ecco il codice pertinente:

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

Quello che devo sapere è se sto usando un approccio corretto e, in caso contrario, cosa dovrei usare per rendere il mio codice thread-safe? Grazie per l'aiuto!


2
(Nota, l' Listinterfaccia non è abbastanza completa per essere molto utile nel multithreading.)
Tom Hawtin - tackline

3
Vorrei solo sottolineare che, senza Collections.synchronizedList(), avremmo una condizione di gara REALE qui: P
Dylan Watson

Risposte:


145

Usa Collections.synchronizedList().

Ex:

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

2
Grazie! Non sono sicuro del motivo per cui non ho pensato di usare solo un vettore poiché ricordo di aver letto da qualche parte che erano sincronizzati.
ericso

32
Forse non è una buona idea lavorare con classi definite deprecate
frandevel

1
Sebbene Vector sia piuttosto vecchio e non supporti il ​​supporto per le raccolte, non è deprecato. Probabilmente è meglio usare Collections.synchronizedList () come altre persone hanno detto qui.
Asturio

14
-1 per i commenti. Vector non è deprecato e in che modo non supporta le raccolte? Implementa List. Il javadoc per Vector dice specificamente: "A partire dalla piattaforma Java 2 v1.2, questa classe è stata adattata per implementare l'interfaccia List, rendendola un membro del Java Collections Framework. A differenza delle nuove implementazioni della raccolta, Vector è sincronizzato." Ci possono essere buone ragioni per non usare Vector (evitare la sincronizzazione, cambiare le implementazioni), ma essere "obsoleti" o "non moderni" non è uno di questi.
fool4jesus

1
Usa i seguenti metodi: Collections.synchronizedList (list); Collections.synchronizedSet (set); Collections.synchronizedMap (mappa); I metodi precedenti accettano la raccolta come parametro e restituiscono lo stesso tipo di raccolta sincronizzata e thread-safe.
Sameer Kazi

35

Modificare

private ArrayList finishingOrder;

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

per

private List finishingOrder;

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

List è un supertipo di ArrayList quindi è necessario specificarlo.

Altrimenti, quello che stai facendo sembra a posto. Un'altra opzione è che puoi usare Vector, che è sincronizzato, ma questo è probabilmente quello che farei io.


1
O Listprobabilmente sarebbe più utile. Oppure List<RaceCar>.
Tom Hawtin - tackline

Buon punto, rendilo privato List finishOrder = Collections.synchronizedList (...)
Reverend Gonzo

Ho provato questo e il compilatore ora si lamenta del fatto che chiamo metodi ArrayList su una raccolta: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); sta dicendo che il metodo get (0) non è stato trovato. Pensieri?
ericso

Ci scusiamo per l'eliminazione e l'aggiunta di nuovo del mio commento. Stavo cercando di far funzionare l'evidenziazione usando i backtick. Mi viene il disturbo ossessivo compulsivo per quel genere di cose.
ericso

No, non funziona. Non esegue il cast della raccolta in un elenco: Race.java:41: tipi incompatibili trovati: java.util.Collection richiesto: java.util.List finishOrder = Collections.synchronizedCollection (new ArrayList (numberOfRaceCars));
ericso

11

CopyOnWriteArrayList

Usa CopyOnWriteArrayListclasse. Questa è la versione thread-safe di ArrayList.


3
Pensaci due volte quando consideri questa lezione. Per citare il documento di classe: "Questo è normalmente troppo costoso, ma può essere più efficiente delle alternative quando le operazioni di attraversamento superano di gran lunga le mutazioni, ed è utile quando non puoi o non vuoi sincronizzare gli attraversamenti, ma devi precludere l'interferenza tra thread simultanei . " Inoltre, vedi Differenza tra CopyOnWriteArrayList e synchronizedList
Basil Bourque

questa classe entra in gioco quando modifichi raramente l'elenco, ma spesso ripeti gli elementi. ad es. quando hai un gruppo di ascoltatori. li registri e poi itererai molto ..., se non hai bisogno esplicitamente dell'interfaccia della lista, ma modifichi e leggi le operazioni per essere simultanee, consideraConcurrentLinkedQueue
benez

7

Si potrebbe essere utilizzando l'approccio sbagliato. Solo perché un thread che simula un'auto finisce prima di un altro thread di simulazione di un'auto, non significa che il primo thread debba vincere la gara simulata.

Dipende molto dalla tua applicazione, ma potrebbe essere meglio avere un thread che calcoli lo stato di tutte le auto a piccoli intervalli di tempo fino al termine della gara. Oppure, se preferisci utilizzare più thread, potresti fare in modo che ciascuna macchina registri il tempo "simulato" impiegato per completare la gara e scegli il vincitore come quello con il tempo più breve.


È un buon punto. Questo è solo un esercizio tratto da un testo che sto usando per imparare Java. Il punto era imparare a usare i thread e in realtà sto andando oltre le specifiche originali del problema nella costruzione di un meccanismo per registrare i vincitori. Ho pensato di usare un timer per misurare i vincitori. Ma onestamente, penso di aver ottenuto ciò di cui ho bisogno dall'esercizio.
ericso

5

Puoi anche usare la synchronizedparola chiave per un addFinishermetodo come questo

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

Quindi puoi usare ArrayList aggiungi il metodo thread-safe in questo modo.


4
bene, ma se avessi due metodi: addFinisher e delFinisher? Entrambi i metodi sono thread-safe ma poiché entrambi accedono allo stesso ArrayList potresti comunque avere problemi.
masi

1
@masi Quindi ti sincronizzi su un final Objectinvece ogni volta che accedi a Collectionin qualsiasi modo.
mkuech

2

Ogni volta che si desidera utilizzare la versione ant thread safe dell'oggetto raccolta ant, consultare il pacchetto java.util.concurrent. * . Ha quasi tutte le versioni simultanee di oggetti di raccolta non sincronizzati. ad esempio: per ArrayList, hai java.util.concurrent.CopyOnWriteArrayList

Puoi fare Collections.synchronizedCollection (qualsiasi oggetto di raccolta), ma ricorda questo classico synchr. la tecnica è costosa e comporta un sovraccarico di prestazioni. Il pacchetto java.util.concurrent. * è meno costoso e gestisce le prestazioni in modo migliore utilizzando meccanismi come

copy-on-write, compare-and-swap, Lock, snapshot iterators, ecc.

Quindi, preferisci qualcosa dal pacchetto java.util.concurrent. *


1

Puoi anche usare come vettore invece, poiché i vettori sono thread-safe e gli arraylist non lo sono. Anche se i vettori sono vecchi, possono risolvere facilmente il tuo scopo.

Ma puoi sincronizzare il tuo Arraylist come il codice dato questo:

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

-1

Puoi cambiare da ArrayList a Vector, in cui ogni metodo è sincronizzato.

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

5
Se hai intenzione di suggerire di utilizzare un'altra raccolta, probabilmente Vector è una scelta sbagliata. È una raccolta legacy che è stata adattata al design del nuovo Java Collections Framework. Sono sicuro che ci sono scelte migliori nel pacchetto java.until.concurrent.
Edwin Dalorzo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.