java.util.ConcurrentModificationException is een zeer veel voorkomende uitzondering bij het werken met Java-collectieklassen. Java-collectieklassen zijn fail-fast, wat betekent dat als de Collection wordt gewijzigd terwijl een thread er doorheen loopt met behulp van een iterator, de iterator.next()
een ConcurrentModificationException zal gooien. De uitzondering bij gelijktijdige wijziging kan voorkomen in zowel een multithreaded als een single-threaded Java-programmeeromgeving.
java.util.ConcurrentModificationException
Laten we het scenario van de gelijktijdige wijzigingsuitzondering bekijken aan de hand van een voorbeeld.
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");
}
}
}
}
Het bovenstaande programma zal een java.util.ConcurrentModificationException
gooien wanneer het wordt uitgevoerd, zoals weergegeven in de onderstaande consolelogs.
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)
Vanuit de uitvoeringsstack-trace is het duidelijk dat de uitzondering voor gelijktijdige wijzigingen wordt gegenereerd wanneer we de next()
-functie van de iterator aanroepen. Als je je afvraagt hoe de Iterator controleert op wijzigingen, is de implementatie aanwezig in de AbstractList-klasse, waar een int-variabele modCount wordt gedefinieerd. De modCount geeft aan hoe vaak de grootte van de lijst is gewijzigd. De modCount-waarde wordt gebruikt bij elke oproep van next() om te controleren op wijzigingen in de functie checkForComodification()
. Commentarieer nu het gedeelte van de lijst uit en voer het programma opnieuw uit. Je zult zien dat er nu geen ConcurrentModificationException wordt gegenereerd. Uitvoer:
Map Value:3
Map Value:2
Map Value:4
Omdat we de bestaande sleutelwaarde in myMap bijwerken, is de grootte niet gewijzigd en krijgen we geen ConcurrentModificationException. De uitvoer kan verschillen op jouw systeem omdat de sleutelset van HashMap niet geordend is zoals een lijst. Als je de verklaring waar ik een nieuwe sleutel-waarde aan de HashMap toevoeg, uitcommentarieert, zal dit een ConcurrentModificationException veroorzaken.
Om ConcurrentModificationException te voorkomen in een multithread-omgeving
- Je kunt de lijst omzetten naar een array en vervolgens itereren over het array. Deze aanpak werkt goed voor kleine of middelgrote lijsten, maar als de lijst groot is, heeft dit aanzienlijke invloed op de prestaties.
- U kunt de lijst vergrendelen tijdens het itereren door deze in een gesynchroniseerd blok te plaatsen. Deze aanpak wordt niet aanbevolen omdat het de voordelen van multithreading zal stoppen.
- Als u JDK1.5 of hoger gebruikt, kunt u de klassen ConcurrentHashMap en CopyOnWriteArrayList gebruiken. Dit is de aanbevolen aanpak om een gelijktijdige modificatie-uitzondering te voorkomen.
Om ConcurrentModificationException te vermijden in single-threaded omgeving
U kunt de iteratorfunctie remove()
gebruiken om het object uit het onderliggende collectieobject te verwijderen. Maar in dit geval kunt u alleen hetzelfde object verwijderen en geen ander object uit de lijst. Laten we een voorbeeld uitvoeren met Concurrent Collection-klassen.
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());
}
}
De uitvoer van het bovenstaande programma wordt hieronder weergegeven. U kunt zien dat er geen ConcurrentModificationException wordt gegenereerd door het programma.
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
Uit het bovenstaande voorbeeld blijkt duidelijk dat:
-
Concurrent Collection-klassen kunnen veilig worden gewijzigd, ze zullen geen ConcurrentModificationException genereren.
-
In het geval van CopyOnWriteArrayList past de iterator zich niet aan aan de wijzigingen in de lijst en werkt op de oorspronkelijke lijst.
-
In het geval van ConcurrentHashMap is het gedrag niet altijd hetzelfde. Voorwaarde:
if(key.equals("1")){ myMap.remove("3");}
Output is:
Map-waarde: 1 Map-waarde: null Map-waarde: 4 Map-waarde: 2 Map-grootte: 4
Het neemt het nieuwe object toegevoegd met de sleutel “4”, maar niet het volgende toegevoegde object met de sleutel “5”. Als ik de voorwaarde verander naar het volgende.
if(key.equals("3")){ myMap.remove("2");}
Output is:
Map-waarde: 1 Map-waarde: 3 Map-waarde: null Map-grootte: 4
In dit geval houdt het geen rekening met de nieuw toegevoegde objecten. Als je ConcurrentHashMap gebruikt, vermijd dan het toevoegen van nieuwe objecten, omdat deze afhankelijk kunnen zijn van de keyset. Merk op dat hetzelfde programma verschillende waarden kan afdrukken op je systeem omdat de HashMap keyset niet geordend is.
Gebruik een for-lus om java.util.ConcurrentModificationException te vermijden
Als je in een single-threaded omgeving werkt en wilt dat je code rekening houdt met de extra toegevoegde objecten in de lijst, dan kun je dit doen met behulp van een for-lus in plaats van een 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");
}
}
Houd er rekening mee dat ik de teller verlaag omdat ik hetzelfde object verwijder, als je het volgende of verder gelegen object wilt verwijderen, hoef je de teller niet te verlagen. Probeer het zelf. 🙂 Nog iets: Je krijgt een ConcurrentModificationException als je probeert de structuur van de oorspronkelijke lijst te wijzigen met behulp van subList. Laten we dit eens bekijken aan de hand van een eenvoudig voorbeeld.
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");
// controleer de onderstaande uitvoer. :)
System.out.println(names + " , " + first2Names);
// Laten we de grootte van de lijst wijzigen en een ConcurrentModificationException krijgen
names.add("NodeJS");
System.out.println(names + " , " + first2Names); // this line throws exception
}
}
De uitvoer van het bovenstaande programma is:
[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)
Volgens de documentatie van ArrayList subList is het alleen toegestaan om structurele wijzigingen aan te brengen in de lijst die wordt geretourneerd door de subList-methode. Alle methoden op de geretourneerde lijst controleren eerst of de werkelijke modCount van de ondersteunende lijst gelijk is aan de verwachte waarde en gooien een ConcurrentModificationException als dit niet het geval is.
Je kunt alle voorbeeldcode downloaden van onze GitHub Repository.
Source:
https://www.digitalocean.com/community/tutorials/java-util-concurrentmodificationexception