Collections.synchronizedList und synchronisiert


68
List<String> list = Collections.synchronizedList(new ArrayList<String>());
synchronized (list) {
    list.add("message");
}

Wird der Block "synchronisiert (Liste) {}" hier wirklich benötigt?

Antworten:


97

Sie müssen nicht synchronisieren, wie Sie in Ihrem Beispiel angegeben haben. Sehr wichtig ist jedoch, dass Sie die Liste synchronisieren müssen, wenn Sie sie wiederholen (wie im Javadoc angegeben):

Es ist unbedingt erforderlich, dass der Benutzer die zurückgegebene Liste manuell synchronisiert, wenn er darüber iteriert:

List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());   
}

14
Link zu der obigen Aussage: docs
Martin Andersson

1
Ein häufiger Anwendungsfall bei einer synchronisierten Sammlung wie dieser besteht darin, die Liste in mehreren Threads zu erweitern, sie jedoch erst am Ende zu wiederholen, wenn alle Aufgaben abgeschlossen sind. In diesem Fall sehe ich keinen Grund, die Iteration zu synchronisieren, da dies von einem einzelnen Thread aus erfolgt.
Desty

3
Solange Sie wissen, dass die Liste während der Iteration nicht aktualisiert wird, müssen Sie sie nicht synchronisieren. Ich weiß jedoch nicht, ob ich diesen Anwendungsfall als "häufig" bezeichnen würde. (Ich habe ConcurrentModificationException schon einige Male gesehen.) Was verhindert in dem von Ihnen erwähnten Anwendungsfall, dass ein Thread erneut zur Liste hinzugefügt wird, während der andere Thread iteriert? Und woher "weiß" der iterierende Thread, wann die anderen Threads die Liste aktualisiert haben?
Sam Goldberg

In ähnlicher Weise muss für Collections.synchornizedMap ein synchronisiertes Schlüsselwort für die Iteration oder Manipulation von Map
pramodc84 verwendet werden.

Wenn die Liste nicht synchronisiert ist, gibt es dann keine Ausnahme für gleichzeitige Änderungen?
user9791370

28

Es kommt auf den genauen Inhalt des synchronizedBlocks an:

  1. Wenn der Block eine einzelne atomare Operation für die Liste ausführt (wie in Ihrem Beispiel), synchronizedist die überflüssig.

  2. Wenn der Block führt auf der Liste mehrere Operationen - und Bedürfnisse die Sperre für die Dauer der Verbindung Betrieb aufrecht zu erhalten - dann das synchronizedist nicht überflüssig. Ein häufiges Beispiel hierfür ist das Durchlaufen der Liste.


21

Der zugrunde liegende Code für die Additionsmethode Collections.synchronizedList lautet:

public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}

In Ihrem Beispiel ist es also nicht erforderlich, eine Synchronisierung hinzuzufügen.


Und was wäre ein Beispiel für einen verwendeten Mutex?
Anoopelias

4
Der Mutex ist die Sammlung selbst (dies), wie im Javadoc dokumentiert.
Assylias

16

Es ist auch wichtig zu beachten, dass alle Methoden, die Iteratoren verwenden, z. B. Collections.sort (), ebenfalls in einem synchronisierten Block gekapselt werden müssen.


Ab Java 8 wurde die Sortiermethode zur List-Schnittstelle hinzugefügt, und diese Methode wird bereits im Mutex synchronisiert. docs.oracle.com/javase/8/docs/api/java/util/…
Bogdan T.

7

Lesen Sie dieses Oracle-Dokument

Es heißt "Es ist unbedingt erforderlich, dass der Benutzer die zurückgegebene Liste manuell synchronisiert, wenn er darüber iteriert."


1
Dieses Dokument klingt eher nach einem Dogma als nach einer Erklärung. Deshalb habe ich diese Diskussion gefunden.
Beemaster

1

Wie von anderen erwähnt, sind die synchronisierten Sammlungen threadsicher , aber die zusammengesetzten Aktionen für diese Sammlungen sind standardmäßig nicht garantiert threadsicher.

Laut JCIP können die üblichen zusammengesetzten Aktionen sein

  • Wiederholung
  • Navigation
  • Put-if-abwesend
  • check-then-act

Der synchronisierte Codeblock des OP ist keine zusammengesetzte Aktion, also kein Unterschied, ob er hinzugefügt wird oder nicht.

Nehmen wir das Beispiel von JCIP und ändern Sie es ein wenig, um zu verdeutlichen, warum es notwendig ist, die zusammengesetzten Aktionen mit Sperre zu schützen.

Es gibt zwei Methoden, die mit derselben Sammlung arbeiten list, die von umbrochen wurdeCollections.synchronizedList

public Object getLast(List<String> list){
    int lastIndex = list.size() - 1;
    return list.get(lastIndex);
}

public void deleteLast(List<String> list){
    int lastIndex = list.size() - 1;
    list.remove(lastIndex);
}

Wenn Methoden getLastund deleteLastgleichzeitig von zwei verschiedenen Threads aufgerufen werden, können die folgenden Zwischenblätter auftreten und getLastwerden ausgelöst ArrayIndexOutOfBoundsException. Angenommen, der Strom lastIndexbeträgt 10.

Thread A (deleteLast) ->
Thread B (getLast) entfernen --------------------> get

Der Faden A. remove das Element vor dem getBetrieb in Thread B. Somit noch der Thread B 10 , wie die Verwendung lastIndexzu nennen list.getVerfahren, wird es gleichzeitig Problem führen.

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.