Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException

Diskutiere Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException im Java Basics - Anfänger-Themen Bereich.
B

BigMemo007

Hallo liebe Gemeinde,

habe das Spiel Snake nachprogrammiert. Damit die Schlange nach jedem Fressen Aufsammeln wachsen kann, wird in eine ArrayList ein neues Schwanzteil hinzugefügt. Dort werden auch die Koordinaten der Schwänze festgehalten. Es gibt - glaube ich- drei Threads, die auf diese ArrayList zugreifen. Einmal meine GameClock - eigenes Thread -, die nach einem Fressen die Methode addTail aufruft. Dann das Haupthread, die x- und y- Koordinate für die Bewegung der Schlange anpasst. Und zum Schluss mein Draw Thread für das Zeichnen in meinem Fenster. Meine Draw-Klasse erweitert JLabel und überschreibt die paintComponent(Graphics g) Methode.

So, ich glaube die o. g. Exception kommt, weil mind. 2 Threads zur gleichen Zeit auf die ArrayList zugreifen wollen. Das geht wohl nicht. Die Draw-Klasse hat repaint(), so dass sie vielleicht 1000 mal in der Sekunde ausgeführt wird und die GameClock stoppt alle 200 Milisekunden. Mein HauptThread greift auf die ArrayList per Aufruf, so dass es nicht so häufig passiert. Ich vermute, dass der Fehler deswegen meistens ab 30 Aufsammeln auftritt.


Das ist der gesamte Fehlerlauf:
Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1012)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:966)
at gui.MyDraw.paintComponent(MyDraw.java:40)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedFPScales(RepaintManager.java:1707)
at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1616)
at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556)
at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323)
at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203)
at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013)
at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:865)
at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848)
at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823)
at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772)
at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Ich habe versucht in meiner MyDras-Klasse (oben fett markiert), folgende synchronisation zu machen:

Java:
g.setColor(new Color(51,204,51));
        synchronized (MySnake.tails) {
            for (MyTail t : MySnake.tails) {
                g.fillRect(MyKoordinate.xPoint(t.getX()), MyKoordinate.yPoint(t.getY()), MyFrame.feldGr,
                        MyFrame.feldGr);
            }
        }
Trotzdem ist der Fehler aufgetaucht. Wie kann ich meine Synchronisation so setzen, dass der Fehler nicht mehr passiert?

Oder soll ich in der MyDraw-Klasse eine neue Array-List erstellen, die eine Kopie der originalen ist? D.h. die x- und y-Werte werden in der Originalen gesetzt, die Kopie-ArrayList holt sich die Werte von der originalen, und die for-Schleife zum Zeichnen greift auf die Kopie, dass es ein gleichzeitiger Doppelaufruf einer ArrayList nicht geben kann.

Oder liege ich mit meiner Vermutzung absolut falsch, dass der Fehler wegen gleichzeitigem Zugriff auf die ArrayList MySnake.tails entseht?

Lieben Dank :))
 
J

JustNobody

Das ist richtig: Der Fehler tritt auf, weil die ArrayList verändert wird, während diese durchlaufen wird. Das ist etwas, das eine ArrayList nicht mag.

Das Problem solltest Du beheben können, indem Du beim Durchlaufen der Liste einen Iterator nutzt. Der verkraftet meines Wissens nach die Veränderungen.

Wenn Du es per Stream machst, sollte es auch funktionieren, aber das habe ich so noch nicht getestet. Also sowas wie:
MySnake.tails.stream().forEach(t ->g.fillRect(MyKoordinate.xPoint(t.getX()), MyKoordinate.yPoint(t.getY()), MyFrame.feldGr, MyFrame.feldGr));
und wenn dann die List verändert wird, gibt es keine Exception. Wäre ggf. ein Test wert.

Das ist jetzt aber einfach mal so im Forum zusammen gedacht und nichts getestet oder so... Tippfehler sind also möglich ...
 
H

httpdigest

Das Problem solltest Du beheben können, indem Du beim Durchlaufen der Liste einen Iterator nutzt.
Nein. Er nutzt ja einen Iterator mit der enhanced for-loop. Und dieser wirft auch die ConcurrentModificationException.
(hint: die enhanced for-loop (oder auch foreach-loop) ist nur syntaktischer Zucker für eine Schleife, die einen Iterator eines Iterable durchläuft).
 
H

httpdigest

Ich habe versucht in meiner MyDras-Klasse (oben fett markiert), folgende synchronisation zu machen:
Java:
g.setColor(new Color(51,204,51));
        synchronized (MySnake.tails) {
            for (MyTail t : MySnake.tails) {
                g.fillRect(MyKoordinate.xPoint(t.getX()), MyKoordinate.yPoint(t.getY()), MyFrame.feldGr,
                        MyFrame.feldGr);
            }
        }
Wenn du es mit synchronized lösen willst, dann reicht das alleine ja nicht.
Du musst auch überall dort, wo du die Liste änderst, auf dasselbe Objekt synchronisieren. Ein synchronized-Block sorgt dafür, dass zu jedem Zeitpunkt nur ein einzelner Thread den Monitor des im synchronized genannten Objektes bekommen kann und den Block betreten kann.
Wenn du nur in deiner paintComponent() Methode auf das Objekt synchronisierst, dann können trotzdem soviele Threads wie möglich das Objekt ändern, da sie sich ja vermutlich nicht auch auf das Objekt synchronisieren.
 
J

JustNobody

Nein. Er nutzt ja einen Iterator mit der enhanced for-loop. Und dieser wirft auch die ConcurrentModificationException.
(hint: die enhanced for-loop (oder auch foreach-loop) ist nur syntaktischer Zucker für eine Schleife, die einen Iterator eines Iterable durchläuft).
Hast natürlich Recht. Wenn man schnell etwas schreiben will ohne sich da Zeit nehmen zu können, dann kommt so ein Müll raus.

Das mit dem Iterator kann nur das Löschen eines Elements in der Schleife lösen, weil der Iterator auch eine remove Operation bietet (Optional, also je nach Implementation kann das auch nicht implementiert sein!) Aber das ist hier ja auch nicht der Fall, daher war die Antwort natürlich komplett unsinnig.
 
B

BigMemo007

Danke für die Antworten.

Seit dem ich statt der for-each Schleife eine normale for-Schleife nutze, tauchte das Problem nicht mehr auf. Die Synchronisation habe ich komplett rauagenommen. Kann natürlich bis jetzt Zufall gewesen sein.ich werde es beobachten.

Also, statt
Java:
for (MyTail t : MySnake.tails) {
   // abarbeiten
}
dies hier
Java:
for (int i=0; i < MySnake.tails.getSize(); i++) {
  //abarbeiten
}
Entschuldigt bitte, wenn ich mich kurz fasse oder Fehler drin hab. Ich schreibe gerade mit meinem Android Devise.
 
mrBrown

mrBrown

Seit dem ich statt der for-each Schleife eine normale for-Schleife nutze, tauchte das Problem nicht mehr auf. Die Synchronisation habe ich komplett rauagenommen. Kann natürlich bis jetzt Zufall gewesen sein.ich werde es beobachten.
Der Fehler wird nicht mehr geworfen – das zugrundeliegende Problem, das Ändern der Liste während du sie benutzt, ist aber weiterhin vorhanden. Anstatt der deutlich Exception wird das aber jetzt zu Fehlern an anderer Stelle führen, deren Ursprung dann nicht mehr so leicht zu finden ist.
 
B

BestGoalkeeper

das zugrundeliegende Problem, das Ändern der Liste während du sie benutzt, ist aber weiterhin vorhanden
Nö. Anstatt vorwärts zu laufen, einfach schreiben:
Java:
for (int i = tails.size() - 1; i >= 0; i--) {
	// abarbeiten
	if (tails.get(i) == 0) {
		tails.remove(i);
	}
}
führt zu überhaupt keinen Problemen und ist standardmäßiges Vorgehen...
 
mrBrown

mrBrown

Nö. Anstatt vorwärts zu laufen, einfach schreiben:
Java:
for (int i = tails.size() - 1; i >= 0; i--) {
    // abarbeiten
    if (tails.get(i) == 0) {
        tails.remove(i);
    }
}
führt zu überhaupt keinen Problemen und ist standardmäßiges Vorgehen...
Nein, du hast nur nicht verstanden, was das Problem ist.
Die Liste wird von zwei Threads gleichzeitig bearbeitet, nicht innerhalb der iteration selbst wird die Liste verändert. Dabei ist’s völlig egal, in welche Richtung man läuft, je nach ausgeführter Aktion gib ne IndexOutOfBoundException oder man bearbeitet zu viele/zu weniger Elemente.
 
Thema: 

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben