Gleichzeitige Änderungsausnahme

8

Ich arbeite gerade an einer Multithread-Anwendung, und gelegentlich erhalte ich eine Ausnahme für die gleichzeitige Änderung (ungefähr einmal oder zweimal pro Stunde, aber in scheinbar zufälligen Intervallen).

Die fehlerhafte Klasse ist im Wesentlichen ein Wrapper für eine Map - der LinkedHashMap erweitert (mit accessOrder auf true gesetzt). Die Klasse hat einige Methoden:

%Vor%

Die Methode set fügt der internen Map ein Schlüssel / Wert-Paar hinzu und ist durch das synchronisierte Schlüsselwort geschützt.

%Vor%

Die get-Methode gibt den Wert basierend auf dem Eingabeschlüssel zurück.

%Vor%

Die interne Map wird gelegentlich neu erstellt (~ alle 2 Minuten, Intervalle stimmen nicht mit den Ausnahmen überein). Die Neuerstellungsmethode erstellt die Werte basierend auf ihren Schlüsseln im Wesentlichen neu. Da rebuild () ziemlich teuer ist, habe ich kein synchronisiertes Schlüsselwort auf die Methode gesetzt. Stattdessen mache ich:

%Vor%

Die Ausnahme tritt bei

auf %Vor%

Innerhalb des synchronisierten Blocks.

Vorschläge werden sehr geschätzt. Zögern Sie nicht, mich auf bestimmte Seiten / Kapitel in Effektives Java und / oder Parallelität in der Praxis hinzuweisen.

Update 1:

Sanitized Stacktrace:

%Vor%

Update 2:

Danke allen für die Antworten bisher. Ich denke, das Problem liegt in der LinkedHashMap und ihrem accessOrder-Attribut, obwohl ich nicht ganz sicher bin, ob es sich um eine Untersuchung handelt.

Wenn accessOrder auf einer LinkedHashMap auf true gesetzt ist und ich auf dessen keySet zugreife, fahre fort, um das keySet über addAll einer LinkedList hinzuzufügen, mutiere eine dieser Aktionen die Reihenfolge mutieren (zähle auf einen "access") ?

    
Cambium 06.07.2009, 22:44
quelle

12 Antworten

7

Wenn Sie LinkedHashMap mit accessOrder = true erstellt haben, mutiert LinkedHashMap.get () die LinkedHashMap tatsächlich, da sie den zuletzt aufgerufenen Eintrag am Anfang der verknüpften Liste von Einträgen speichert. Vielleicht ruft irgendwas get () auf, während die Array-Liste ihre Kopie mit dem Iterator erstellt.

    
Sean McCauliff 06.07.2009, 23:49
quelle
6

Diese Ausnahme hat normalerweise nichts mit der Synchronisation zu tun - sie wird normalerweise ausgelöst, wenn eine Collection geändert wird, während ein Iterator sie durchläuft. AddAll-Methoden können einen Iterator verwenden - und es ist erwähnenswert, dass die noble Foreach-Schleife auch über Instanzen von Iterator iteriert.

z.B.:

%Vor%

reicht aus, um die Ausnahme für einige Sammlungen (z. B. ArrayList) zu erhalten.

James

    
gubby 06.07.2009 23:22
quelle
2

Sind all diese Funktionen in Ihrem Wrapper? Weil diese Ausnahme ausgelöst werden könnte, während Sie irgendwie an anderer Stelle über die Sammlung iterieren. Und ich nehme an, Sie haben die Methode mit möglichen offensichtlichen Rassenbedingungen synchronisiert, aber wahrscheinlich weniger offensichtliche Fälle verpasst. Hier der Verweis auf die Dokumente der Ausnahmeklasse.

    
Artem Barger 06.07.2009 22:57
quelle
1

Aus dem Javadoc:

  

Wenn mehrere Threads gleichzeitig auf eine verknüpfte Hash-Map zugreifen und mindestens einer der Threads die Map strukturell ändert, muss sie extern synchronisiert werden. Dies wird typischerweise durch Synchronisieren mit einem Objekt erreicht, das die Karte natürlich kapselt. Wenn kein solches Objekt vorhanden ist, sollte die Map mit der Collections.synchronizedMap-Methode "umgebrochen" werden. Dies geschieht am besten zum Zeitpunkt der Erstellung, um einen versehentlichen unsynchronisierten Zugriff auf die Karte zu verhindern:

     

Map m = Collections.synchronizedMap (neue LinkedHashMap (...));

Es ist möglicherweise sicherer, die LinkedHashMap tatsächlich zu umbrechen, anstatt zu behaupten, dass Sie sie erweitern. Ihre Implementierung würde also ein internes Datenelement haben, das die Map ist, die von Collections.synchronizedMap (new LinkedHashMap (...)) zurückgegeben wird.

Einzelheiten finden Sie im Collections javadoc: Collections.synchronizedMap

    
Tim Bender 07.07.2009 01:45
quelle
0

versuche das:

%Vor%     
Oso 07.07.2009 01:18
quelle
0

Behalten Sie den Zugriff auf internalMap syncronized andernfalls tritt java.util.ConcurrentModificationException auf, weil HashMap # modCount (das strukturelle Änderungen protokolliert) gleichzeitig während der Keyset-Iteration geändert werden kann (aufgrund keysCopy.addAll (internalMap.keySet ( ) Aufruf).

LinkedHashMap javaDoc gibt Folgendes an: "In durch Zugriff sortierten verknüpften Hash-Maps ist die bloße Abfrage der Karte mit get eine strukturelle Änderung."

    
Loop 07.07.2009 13:56
quelle
0

Versuchen Sie, das synchronisierte Schlüsselwort aus den Methoden set () und get () zu entfernen, und verwenden Sie stattdessen einen synchronisierten Block innerhalb der Methode, der internalMap sperrt; Ändern Sie dann die synchronisierten Blöcke der rebuild () -Methode, um auch die interne Karte zu sperren.

    
Chochos 06.07.2009 22:50
quelle
0

das ist seltsam. Ich sehe keine Möglichkeit, dass die Ausnahme an dem Ort ausgelöst wird, den Sie identifizieren (in der Standardimplementation in Java 6). erhalten Sie eine ConcurrentModificationException in der ArrayList oder der HashMap? hast du die Methode keySet() in deinem HashMap überschrieben?

edit mein Fehler - die ArrayList wird das KeySet zur Iteration zwingen (AbstractCollection.toArray () wird über seine Schlüssel iterieren)

Es gibt irgendwo im Code, mit dem Sie Ihre interne Karte aktualisieren können, die nicht synchronisiert ist.

  • Haben Sie eine Hilfsmethode, die Ihre interne Karte freigibt, die "nicht verwendet werden soll" oder
  • ist Ihre interne Karte, die als öffentlicher Bereich
  • identifiziert wurde
akf 06.07.2009 22:59
quelle
0

Ist internalMap statisch? Möglicherweise verfügen Sie über mehrere Objekte, von denen jedes auf this , dem Objekt, sperrt, aber keine korrekte Sperre für Ihre statische interneMap bereitstellt.

    
Stephen Denne 06.07.2009 23:47
quelle
0

Versuchen Sie, die Karte als vorübergehend zu deklarieren

    
Reed 07.07.2009 00:54
quelle
0

Beim Iterieren über die Tasten aufgrund von

%Vor%

Entscheiden Sie, dass einige der Einträge aus der LinkedHashMap entfernt werden können?

Die removeEldestEntry Methode könnte entweder true zurückgeben oder die Map modifizieren, wodurch die Iteration gestört wird.

    
Stephen Denne 07.07.2009 00:45
quelle
0

Ich glaube nicht, dass Ihre set() / get() mit dem gleichen Monitor synchronisiert werden, den rebuild() ist. Dies ermöglicht es jemandem, set / get aufzurufen, während die problematische Zeile ausgeführt wird, insbesondere beim Iterieren über den Schlüsselsatz der internalMap während des Aufrufs addAll() (interne Implementierung, die durch Ihre Stack-Ablaufverfolgung verfügbar gemacht wird).

Anstatt set() synchronisiert zu machen, haben Sie versucht, etwas wie folgt auszuführen:

%Vor%

Ich denke nicht, dass Sie get() synchronisieren müssen, aber wenn Sie darauf bestehen:

%Vor%

Tatsächlich denke ich, dass es besser ist, mit internalMap anstatt mit this zu synchronisieren. Es macht auch nicht weh, internalMap volatile zu machen, obwohl ich nicht denke, dass dies wirklich notwendig ist, wenn Sie sicher sind, dass set() / get() / rebuild() die einzigen Methoden sind, die direkt auf internalMap zugreifen, und alle greifen synchron darauf zu.

%Vor%     
Jack Leow 07.07.2009 00:37
quelle

Tags und Links