java.util.ConcurrentModificationException

java.util.ConcurrentModificationException ist eine sehr häufige Ausnahme bei der Arbeit mit Java-Sammlungsklassen. Java-Sammlungsklassen sind fail-fast, was bedeutet, dass, wenn die Sammlung geändert wird, während ein Thread darüber iteriert, indem er den Iterator verwendet, wird iterator.next() eine ConcurrentModificationException auslösen. Die Ausnahme bei gleichzeitigen Änderungen kann sowohl in einer mehrfädigen als auch in einer einzelnen Java-Programmumgebung auftreten.

java.util.ConcurrentModificationException

Lassen Sie uns das Szenario der gleichzeitigen Änderungsausnahme anhand eines Beispiels betrachten.

package com.journaldev.ConcurrentModificationException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ConcurrentModificationExceptionExample {

	public static void main(String args[]) {
		List myList = new ArrayList();

		myList.add("1");
		myList.add("2");
		myList.add("3");
		myList.add("4");
		myList.add("5");

		Iterator it = myList.iterator();
		while (it.hasNext()) {
			String value = it.next();
			System.out.println("List Value:" + value);
			if (value.equals("3"))
				myList.remove(value);
		}

		Map myMap = new HashMap();
		myMap.put("1", "1");
		myMap.put("2", "2");
		myMap.put("3", "3");

		Iterator it1 = myMap.keySet().iterator();
		while (it1.hasNext()) {
			String key = it1.next();
			System.out.println("Map Value:" + myMap.get(key));
			if (key.equals("2")) {
				myMap.put("1", "4");
				// myMap.put("4", "4");
			}
		}

	}
}

Das obige Programm wird beim Ausführen eine java.util.ConcurrentModificationException auslösen, wie in den folgenden Konsolenprotokollen gezeigt.

List Value:1
List Value:2
List Value:3
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
	at com.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionExample.main(ConcurrentModificationExceptionExample.java:22)

Aus dem Ausgabestapel ist klar ersichtlich, dass die ConcurrentModificationException ausgelöst wird, wenn wir die Funktion iterator next() aufrufen. Wenn Sie sich fragen, wie der Iterator die Änderung überprüft, ist die Implementierung in der Klasse AbstractList vorhanden, in der eine int-Variable modCount definiert ist. Der Wert von modCount gibt an, wie oft die Größe der Liste geändert wurde. Der Wert von modCount wird bei jedem Aufruf von next() verwendet, um Änderungen in der Funktion checkForComodification() zu überprüfen. Kommentieren Sie nun den Listenteil aus und führen Sie das Programm erneut aus. Sie werden feststellen, dass keine ConcurrentModificationException mehr ausgelöst wird. Ausgabe:

Map Value:3
Map Value:2
Map Value:4

Da wir den vorhandenen Schlüsselwert in myMap aktualisieren, hat sich dessen Größe nicht geändert und wir erhalten keine ConcurrentModificationException. Die Ausgabe kann in Ihrem System unterschiedlich sein, da die Schlüsselreihenfolge von HashMap nicht wie bei einer Liste geordnet ist. Wenn Sie die Anweisung, in der ich einen neuen Schlüsselwert zum HashMap hinzufüge, auskommentieren, wird es zu einer ConcurrentModificationException führen.

Um ConcurrentModificationException in einer Mehr-Thread-Umgebung zu vermeiden

  1. Sie können die Liste in ein Array konvertieren und dann das Array durchlaufen. Dieser Ansatz funktioniert gut für kleine oder mittelgroße Listen, aber wenn die Liste groß ist, beeinträchtigt dies die Leistung erheblich.
  2. Sie können die Liste während der Iteration sperren, indem Sie sie in einen synchronisierten Block setzen. Diese Methode wird nicht empfohlen, da sie die Vorteile von Multithreading beeinträchtigt.
  3. Wenn Sie JDK1.5 oder höher verwenden, können Sie die Klassen ConcurrentHashMap und CopyOnWriteArrayList verwenden. Dies ist der empfohlene Ansatz, um eine gleichzeitige Änderungsausnahme zu vermeiden.

Um ConcurrentModificationException in einer Single-Thread-Umgebung zu vermeiden

Sie können die Funktion iterator remove() verwenden, um das Objekt aus dem zugrunde liegenden Sammlungsobjekt zu entfernen. In diesem Fall können Sie dasselbe Objekt und kein anderes Objekt aus der Liste entfernen. Lassen Sie uns ein Beispiel mit Concurrent Collection-Klassen ausführen.

package com.journaldev.ConcurrentModificationException;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class AvoidConcurrentModificationException {

	public static void main(String[] args) {

		List<String> myList = new CopyOnWriteArrayList<String>();

		myList.add("1");
		myList.add("2");
		myList.add("3");
		myList.add("4");
		myList.add("5");

		Iterator<String> it = myList.iterator();
		while (it.hasNext()) {
			String value = it.next();
			System.out.println("List Value:" + value);
			if (value.equals("3")) {
				myList.remove("4");
				myList.add("6");
				myList.add("7");
			}
		}
		System.out.println("List Size:" + myList.size());

		Map<String, String> myMap = new ConcurrentHashMap<String, String>();
		myMap.put("1", "1");
		myMap.put("2", "2");
		myMap.put("3", "3");

		Iterator<String> it1 = myMap.keySet().iterator();
		while (it1.hasNext()) {
			String key = it1.next();
			System.out.println("Map Value:" + myMap.get(key));
			if (key.equals("1")) {
				myMap.remove("3");
				myMap.put("4", "4");
				myMap.put("5", "5");
			}
		}

		System.out.println("Map Size:" + myMap.size());
	}

}

Die Ausgabe des obigen Programms wird unten angezeigt. Sie können sehen, dass keine ConcurrentModificationException vom Programm ausgelöst wird.

List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Size:6
Map Value:1
Map Value:2
Map Value:4
Map Value:5
Map Size:4

Aus dem obigen Beispiel geht klar hervor, dass:

  1. Concurrent Collection-Klassen können sicher geändert werden, sie werfen keine ConcurrentModificationException.

  2. Bei CopyOnWriteArrayList berücksichtigt der Iterator keine Änderungen in der Liste und arbeitet mit der Originalliste.

  3. Im Fall von ConcurrentHashMap ist das Verhalten nicht immer das Gleiche. Für die Bedingung:

    if(key.equals("1")){
    	myMap.remove("3");}
    

    lautet die Ausgabe:

    Map-Wert: 1
    Map-Wert: null
    Map-Wert: 4
    Map-Wert: 2
    Map-Größe: 4
    

    Es wird das neue Objekt mit dem Schlüssel „4“ berücksichtigt, jedoch nicht das nächste hinzugefügte Objekt mit dem Schlüssel „5“. Wenn ich die Bedingung wie folgt ändere:

    if(key.equals("3")){
    	myMap.remove("2");}
    

    lautet die Ausgabe:

    Map-Wert: 1
    Map-Wert: 3
    Map-Wert: null
    Map-Größe: 4
    

    In diesem Fall werden die neu hinzugefügten Objekte nicht berücksichtigt. Wenn Sie ConcurrentHashMap verwenden, sollten Sie das Hinzufügen neuer Objekte vermeiden, da diese je nach Schlüsselsatz verarbeitet werden können. Beachten Sie, dass dasselbe Programm in Ihrem System unterschiedliche Werte ausgeben kann, da der Schlüsselsatz von HashMap nicht geordnet ist.

Verwenden Sie eine for-Schleife, um java.util.ConcurrentModificationException zu vermeiden.

Wenn Sie in einer single-threaded Umgebung arbeiten und möchten, dass Ihr Code sich um die zusätzlich hinzugefügten Objekte in der Liste kümmert, können Sie dies mit einer for-Schleife tun, anstatt mit einem Iterator.

for(int i = 0; i<myList.size(); i++){
	System.out.println(myList.get(i));
	if(myList.get(i).equals("3")){
		myList.remove(i);
		i--;
		myList.add("6");
	}
}

Beachten Sie, dass ich den Zähler verringere, weil ich dasselbe Objekt entferne. Wenn Sie das nächste oder weitere entfernte Objekt entfernen müssen, müssen Sie den Zähler nicht verringern. Probieren Sie es selbst aus. 🙂 Noch etwas: Sie erhalten eine ConcurrentModificationException, wenn Sie versuchen, die Struktur der Originalliste mit subList zu ändern. Sehen wir uns das anhand eines einfachen Beispiels an.

package com.journaldev.ConcurrentModificationException;

import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExceptionWithArrayListSubList {

	public static void main(String[] args) {

		List names = new ArrayList<>();
		names.add("Java");
		names.add("PHP");
		names.add("SQL");
		names.add("Angular 2");

		List first2Names = names.subList(0, 2);

		System.out.println(names + " , " + first2Names);

		names.set(1, "JavaScript");
		// Überprüfen Sie die Ausgabe unten. :)
		System.out.println(names + " , " + first2Names);

		// Ändern wir die Listenlänge und erhalten ConcurrentModificationException
		names.add("NodeJS");
		System.out.println(names + " , " + first2Names); // this line throws exception

	}

}

Die Ausgabe des obigen Programms lautet:

[Java, PHP, SQL, Angular 2] , [Java, PHP]
[Java, JavaScript, SQL, Angular 2] , [Java, JavaScript]
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1282)
	at java.base/java.util.ArrayList$SubList.listIterator(ArrayList.java:1151)
	at java.base/java.util.AbstractList.listIterator(AbstractList.java:311)
	at java.base/java.util.ArrayList$SubList.iterator(ArrayList.java:1147)
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:465)
	at java.base/java.lang.String.valueOf(String.java:2801)
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)
	at com.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:26)

Laut der ArrayList subList-Dokumentation sind strukturelle Änderungen nur in der Liste zulässig, die von der subList-Methode zurückgegeben wird. Alle Methoden in der zurückgegebenen Liste überprüfen zuerst, ob der tatsächliche modCount der zugrunde liegenden Liste seinem erwarteten Wert entspricht, und werfen eine ConcurrentModificationException, wenn dies nicht der Fall ist.

Sie können den gesamten Beispielcode aus unserem GitHub-Repository herunterladen.

Source:
https://www.digitalocean.com/community/tutorials/java-util-concurrentmodificationexception