Vermeiden von CurrentModificationException

Luk10

Top Contributor
Grüße,

Ich hab eine Liste von zeichenbaren Objekten. Diese werden ständig in der überschriebenen Methode
Code:
drawComponent(Graphics g)
gezeichnet.

Manchmal will ich aber Objekte dieser Liste hinzufügen oder entfernen. Da ich nicht kontrollieren kann wenn (und wann exakt)
Code:
repaint()
aufgerufen wird, bekomme ich sehr häufig einen
Code:
ConcurrentModificationException
was ja verständlich ist, da ich die Liste in einem Thread verändere, während im ATW-EventQueue Thread über sie iteriert wird ...

Wie kann ich dennoch die Liste verändern, ohne diese Exception auszulösen? Man müsste irgendwie verhindern, dass während des Veränderns über die Liste iteriert wird, bzw dass während des Iterierens die Liste verändert wird ...

Leider kenne ich mich da nicht besonders gut aus ...
Kann mir jemand helfen?

Danke,
-Luk10-
 

Luk10

Top Contributor
Hmhm ... ich glaube nicht, da ich

> denke, dass der Fehler beim hinzufügen von Objekten entsteht.
> meineArrayList.clear() aufrufe, also keine geziehlten Objekte entferne, sondern alles raushaben will)
Konkret:


Ich will zuerst alle Objekte entfernen, und dann neue hinzufügen. Beim Hinzufügen bekomme ich den Fehler.

Luk10

EDIT: Ich probiere gerade mit dem iterator rum ...
 
Zuletzt bearbeitet:

Luk10

Top Contributor
Java:
public class SimPanel extends JPanel {
	private static final long serialVersionUID = 1L;
	
	
	private List<Tile> allTiles;

	
	public SimPanel() {
		
		allTiles = null;
		
	}
	
	
	@Override
	public void paintComponent(Graphics g) {
		
		super.paintComponent(g);
		
		if(allTiles != null) {
			
			synchronized (allTiles) {
				
				Iterator<Tile> it = allTiles.iterator();
				while(it.hasNext()) {
					
					it.next().drawTile(g);
					
				}
				
			}
			
		}
		
	}


	public void submitTiles(ArrayList<Tile> allTiles) {
		
		this.allTiles = Collections.synchronizedList(allTiles);
		
	}

}

Hilft nichts!

-Luk10-
 

Marco13

Top Contributor
Zufällig liegt bei mir im Hinterkopf schon seit langem genau diese Frage rum und fängt Staub, einschließlich des kleinen gelben Haftettiketts wo draufsteht "Dazu mal 'nen Thread aufmachen" :D

Die Frage ist nicht so einfach zu beantworten. Grundsätzlich gibt es natürlich einige "pragmatische" Lösungsmöglichkeiten: Nimm' ein synchronized, verwendet Locks, nimm eine CopyOnWriteArrayList etc. (sind ja auch im verlinkten Thread zusammengefasst).

Das Problem dabei ist, dass diese synchronisationen u.U. dramatischen negativen Einfluß auf die Performance haben können. Gerade wenn es wirklich SEHR performant sein muss, und in einem Fall wie dem angedeuteten "viele" (zeichenbare) Objekte sind, sagen wir mal >>10000 oder so, fallen Dinge wie eine CopyOnWriteArrayList schonmal weg. Auch eine pauschale synchronisation bewirkt dann, dass zwei Threads schon nicht mehr gleichzeitig auf die Objekte zugreifen können: Wenn der AWT-Thread 100000 Objekte zeichnen soll, und der GameLogic-Thread möglichst gleichzeitig über dieselben 100000 Objekte laufen soll, und z.B. ihre Position ändern, ist es der Killer, wenn man das durch Synchronisation quasi serialisiert.

Besonders "ärgerlich" ist das, wenn die Liste der Objekte sich "meistens nur sehr selten" ändert - also z.B. das Spiel 10 Minuten läuft, und in der Zeit nur jede Minute einmal 1000 Objekte hinzugefügt oder 1000 entfernt werden: "Die ganze Zeit" synchronisieren, nur weil jede Minute mal was passiert? Neee... CopyOnWriteArrayList, damit das ganze jede Minute mal eine Weile stehenbleibt um etliche MB Daten hin-und-her zu schaufeln? Auch schlecht...

Den ... "interessantesten" Ansatz dazu habe ich bisher in einer sehr verbreiteten und sehr bekannten Graphen-Bibliothek gesehen, nämlich in JUNG - Java Universal Network/Graph Framework : Die haben tatsächlich (an mehreren Stellen - quasi "systematisch") Code nach dem Muster
Java:
void paintAllObjects()
{
    try
    {
        for (Object object : objects)
        {
            paint(object);
        }
    }
    catch (ConcurrentModificationException e)
    {
        paintAllObjects();
    }
}

Jedem Anfänger, der mir hier im Forum oder sonstwo diesen Code vorsetzen würde, würde ich eine Handvoll :autsch:-Smileys um die Ohren werfen :shock: Aber die Idee und Rechtfertigiung ist klar: Das Zeichnen (und alles, was dazu parallel läuft) ist höchst(!)-zeitkritisch. Es ändert sich nur sehr selten was an der Liste. Wenn da mal "was schiefgeht", ignorier' es einfach und versuch's nochmal.

Trotzdem würde mir das bei eigenen Programmen zu viele Bauchschmerzen verursachen...

Der einzige "pragmatische" Ansatz, von dem ich glaube, dass er wirklich potentiell (!) "schön", praktikabel und performant (!) sein könnte, wäre, mit Read/Write locks: Jeder, der über die Liste iteriert, holt sich vorher einen ReadLock - damit können mehrere gleichzeitig drüberlaufen und ggf. Objekteigenschaften ändern. NUR wenn jemand wirklich die Liste ändert, holt er sich (so kurz wie möglich) einen WriteLock.

Aber das ausimplementieren kann aufwändiger (und an manchen Stellen auch ziemlich schwierig) sein...
 

Luk10

Top Contributor
@EikeB

Nein, wie kann ich
Code:
meineListe.clear()
und
Code:
meineListe.add(...)
in den synchronized-Block schreiben?

Muss ich da auch einen Iterator verwenden? Und wenn ja wie?

@Marco13

Danke für deinen Beitrag, hilft leider im Moment nicht sooo viel ;)
 
G

Gast2

Gast
Nein, wie kann ich meineListe.clear() und meineListe.add(...) in den synchronized-Block schreiben?
In DEN synchronized Block müssen die natürlich nicht rein, aber die müssen in einen synchronized Block der auf das selbe Objekt synchronisiert.
Die Nachteile davon stehen in Marco13s Post.
 

Luk10

Top Contributor
Das ist mir klar ... ersetzte "den" mit "einen" und beantworte meine Frage :)

Hab grade folgendes erfolgreich getestet, jedoch nicht wirklich zufriedenstellend:

Java:
			simFrame.getSimPanel().lock(true);
			
			try {
				
				Thread.sleep(200);
				
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
			
			allTiles.clear();
			creationBehavior.createTilePattern();
			
			simFrame.getSimPanel().lock(false);

> Ich setzt [boolean] locked = true, was die Iteration über die Liste blockiert
> Ich warte 200 Millis, damit hoffentlich fertig iteriert wurde, falls ich locked = true mitten in der Iteration gesetzt habe
> Ich verändere die Liste
> Ich erlaube das Iterieren wieder

Leider sind 200 Millis eine lange und unpräzise Zeit, kann auch - falls es noch länger dauert - schief gehen.
 

bERt0r

Top Contributor
Kannst du dir nicht einfach eine zweite Liste machen und nach jedem repaintAufruf noch einen addAll(zweiteListe) Aufruf dranhängen?
 

Ähnliche Java Themen


Oben