Jak sprawić, by moja ArrayList była bezpieczna dla wątków? Inne podejście do problemu w Javie?


90

Mam ArrayList, którego chcę użyć do przechowywania obiektów RaceCar, które rozszerzają klasę Thread, gdy tylko zostaną zakończone. Klasa o nazwie Race obsługuje tę ArrayList przy użyciu metody wywołania zwrotnego, którą obiekt RaceCar wywołuje po zakończeniu wykonywania. Metoda wywołania zwrotnego addFinisher (RaceCar finisher) dodaje obiekt RaceCar do ArrayList. Ma to na celu zapewnienie kolejności, w jakiej wątki kończą wykonywanie.

Wiem, że ArrayList nie jest zsynchronizowany, a zatem nie jest bezpieczny dla wątków. Próbowałem użyć metody Collections.synchronizedCollection (c Collection), przekazując nową ArrayList i przypisując zwróconą Collection do ArrayList. Jednak to daje mi błąd kompilatora:

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

Oto odpowiedni kod:

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

Muszę wiedzieć, czy stosuję poprawne podejście, a jeśli nie, czego należy użyć, aby mój kod był bezpieczny dla wątków? Dzięki za pomoc!


2
(Uwaga, Listinterfejs nie jest na tyle kompletny, aby był bardzo przydatny w wielowątkowości.)
Tom Hawtin - tackline

3
Chciałbym tylko zaznaczyć, że bez Collections.synchronizedList()tego mielibyśmy PRAWDZIWE warunki wyścigu tutaj: P
Dylan Watson

Odpowiedzi:


147

Użyj Collections.synchronizedList().

Dawny:

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

2
Dzięki! Nie jestem pewien, dlaczego nie pomyślałem, aby po prostu użyć Vector, skoro pamiętam, że czytałem gdzieś, gdzie zostały zsynchronizowane.
ericso

32
Może nie jest dobrym pomysłem praca z klasami, które są zdefiniowane jako przestarzałe
frandevel

1
Chociaż Vector jest dość stary i nie obsługuje kolekcji, nie jest przestarzały. Prawdopodobnie lepiej jest użyć Collections.synchronizedList (), tak jak powiedzieli inni ludzie.
Asturio,

14
-1 za komentarze. Vector nie jest przestarzały i dlaczego nie obsługuje kolekcji? Implementuje List. Dokument javadoc dla Vector mówi konkretnie: „Począwszy od platformy Java 2 v1.2, ta klasa została zmodernizowana w celu zaimplementowania interfejsu List, dzięki czemu jest członkiem Java Collections Framework. W przeciwieństwie do nowych implementacji kolekcji Vector jest synchronizowany”. Mogą istnieć dobre powody, aby nie używać Vector (unikanie synchronizacji, zmiana implementacji), ale bycie „przestarzałym” lub „nienowoczesnym” nie jest jednym z nich.
fool4jesus

1
Użyj poniższych metod: Collections.synchronizedList (list); Collections.synchronizedSet (zestaw); Collections.synchronizedMap (mapa); Powyższe metody przyjmują kolekcję jako parametr i zwracają ten sam typ kolekcji, które są zsynchronizowane i bezpieczne wątkowo.
Sameer Kazi

35

Zmiana

private ArrayList finishingOrder;

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

do

private List finishingOrder;

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

Lista jest typem nadrzędnym ArrayList, więc musisz to określić.

W przeciwnym razie to, co robisz, wydaje się w porządku. Inną opcją jest użycie Vector, który jest zsynchronizowany, ale prawdopodobnie tak bym zrobił.


1
Albo Listprawdopodobnie byłby bardziej przydatny. Lub List<RaceCar>.
Tom Hawtin - tackline

Słuszna uwaga, uczyń ją prywatną List finishOrder = Collections.synchronizedList (...)
Reverend Gonzo

Wypróbowałem to, a kompilator narzeka, że ​​wywołuję metody ArrayList w kolekcji: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); Mówi, że metoda get (0) nie została znaleziona. Myśli?
ericso

Przepraszam za usunięcie i ponowne dodanie mojego komentarza. Starałem się, aby podświetlenie działało za pomocą grawitacji. Dostaję OCD w tego rodzaju sprawach.
ericso

Nie, to nie działa. Nie rzutuje kolekcji na listę: Race.java:41: znaleziono niezgodne typy: java.util.Collection required: java.util.List finishOrder = Collections.synchronizedCollection (new ArrayList (numberOfRaceCars));
ericso

11

CopyOnWriteArrayList

Użyj CopyOnWriteArrayListklasy. To jest wersja bezpieczna wątkowo ArrayList.


3
Zastanów się dwa razy, rozważając tę ​​klasę. Cytując dokument klasy: „Jest to zwykle zbyt kosztowne, ale może być bardziej wydajne niż alternatywy, gdy operacje przechodzenia znacznie przewyższają liczbę mutacji i jest przydatne, gdy nie możesz lub nie chcesz synchronizować przemierzania, ale trzeba wykluczyć interferencję między współbieżnymi wątkami . ” Zobacz także różnicę między CopyOnWriteArrayList i synchronizedList
Basil Bourque

ta klasa pojawia się, gdy rzadko modyfikujesz listę, ale często powtarzasz elementy. np. gdy masz zestaw słuchaczy. rejestrujesz je, a następnie dużo iterujesz ... jeśli nie potrzebujesz jawnie interfejsu listy, ale modyfikujesz i odczytujesz operacje, aby były współbieżne, rozważConcurrentLinkedQueue
benez

7

Być może używasz złego podejścia. Tylko dlatego, że jeden wątek symulujący samochód kończy się przed innym wątkiem z symulacją samochodu, nie oznacza, że ​​pierwszy wątek powinien wygrać symulowany wyścig.

To zależy w dużej mierze od twojej aplikacji, ale może być lepiej mieć jeden wątek, który oblicza stan wszystkich samochodów w małych odstępach czasu do zakończenia wyścigu. Lub, jeśli wolisz korzystać z wielu wątków, możesz kazać każdemu samochodowi zarejestrować „symulowany” czas potrzebny do ukończenia wyścigu i wybrać zwycięzcę jako zwycięzcę z najkrótszym czasem.


Trafne spostrzeżenie. To tylko ćwiczenie z tekstu, którego używam do nauki języka Java. Chodziło o to, aby nauczyć się obsługi wątków i właściwie wychodzę poza pierwotne specyfikacje problemu w budowaniu mechanizmu do logowania zwycięzców. Pomyślałem o używaniu timera do mierzenia zwycięzców. Ale szczerze mówiąc, myślę, że dostałem to, czego potrzebowałem, z ćwiczenia.
ericso

5

Możesz również użyć synchronizedsłowa kluczowego dla addFinishermetody takiej jak ta

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

Możesz więc w ten sposób użyć ArrayList dodać metodę bezpieczną dla wątków.


4
dobrze, ale co jeśli masz dwie metody: addFinisher i delFinisher? Obie metody są bezpieczne dla wątków, ale ponieważ obie uzyskują dostęp do tej samej tablicy ArrayList, nadal będziesz mieć problemy.
omni

1
@masi Następnie wystarczy zsynchronizować się za final Objectkażdym razem, gdy Collectionw jakikolwiek sposób uzyskujesz dostęp do .
mkuech

2

Zawsze, gdy chcesz użyć bezpiecznej dla wątków wersji obiektu kolekcji Ant, skorzystaj z pomocy pakietu java.util.concurrent. * . Ma prawie wszystkie współbieżne wersje niezsynchronizowanych obiektów kolekcji. np. dla ArrayList masz java.util.concurrent.CopyOnWriteArrayList

Możesz zrobić Collections.synchronizedCollection (dowolny obiekt kolekcji), ale pamiętaj o tym klasycznym synchr. technika jest kosztowna i wiąże się z nadwyżką wydajności. Pakiet java.util.concurrent. * jest tańszy i lepiej zarządza wydajnością za pomocą mechanizmów takich jak

kopiowanie przy zapisywaniu, porównywanie i zamiana, blokowanie, iteratory migawek itp.

Dlatego wolę coś z pakietu java.util.concurrent. *


1

Zamiast tego można również użyć jako Vector, ponieważ wektory są bezpieczne dla wątków, a lista arraylist nie. Chociaż wektory są stare, ale mogą łatwo rozwiązać twój cel.

Ale możesz zsynchronizować Arraylist jak kod, pod warunkiem, że:

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

-1

Możesz zmienić typ ArrayList na Vector, w którym każda metoda jest synchronizowana.

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

5
Jeśli masz zamiar zasugerować użycie innej kolekcji, prawdopodobnie Vector jest złym wyborem. Jest to starsza kolekcja, która została zmodernizowana do projektu nowej struktury kolekcji Java. Jestem pewien, że pakiet java.until.concurrent zawiera lepsze opcje.
Edwin Dalorzo
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.