java.util.ConcurrentModificationException

java.util.ConcurrentModificationException est une exception très courante lors de l’utilisation des classes de collection Java. Les classes de collection Java sont fail-fast, ce qui signifie que si la collection est modifiée pendant que certains threads la parcourent à l’aide d’un itérateur, l’appel de iterator.next() lèvera une ConcurrentModificationException. L’exception de modification concurrente peut survenir dans le cas d’un environnement de programmation Java multithreadé ainsi que monothreadé.

java.util.ConcurrentModificationException

Voyons le scénario d’exception de modification concurrente avec un exemple.

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");
			}
		}

	}
}

Le programme ci-dessus lèvera une java.util.ConcurrentModificationException lors de son exécution, comme indiqué dans les journaux de la console ci-dessous.

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)

À partir de la pile de sortie des traces, il est clair que l’exception de modification concurrente est levée lorsque nous appelons la fonction next() de l’itérateur. Si vous vous demandez comment l’itérateur vérifie la modification, son implémentation est présente dans la classe AbstractList, où une variable int modCount est définie. Le modCount fournit le nombre de fois que la taille de la liste a été modifiée. La valeur de modCount est utilisée à chaque appel de next() pour vérifier s’il y a eu des modifications dans une fonction checkForComodification(). Maintenant, commentez la partie de la liste et exécutez à nouveau le programme. Vous verrez qu’aucune ConcurrentModificationException n’est désormais lancée. Sortie:

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

Puisque nous mettons à jour la valeur clé existante dans myMap, sa taille n’a pas été modifiée et nous n’obtenons pas de ConcurrentModificationException. La sortie peut être différente sur votre système car le HashMap keyset n’est pas ordonné comme une liste. Si vous décommentez l’instruction où j’ajoute une nouvelle paire clé-valeur dans le HashMap, cela provoquera une ConcurrentModificationException.

Pour éviter ConcurrentModificationException dans un environnement multi-thread

  1. Vous pouvez convertir la liste en tableau puis itérer sur le tableau. Cette approche fonctionne bien pour une liste de taille petite ou moyenne, mais si la liste est grande, cela affectera beaucoup les performances.
  2. Vous pouvez verrouiller la liste lors de l’itération en la plaçant dans un bloc synchronisé. Cette approche n’est pas recommandée car elle annulera les avantages du multithreading.
  3. Si vous utilisez JDK1.5 ou une version supérieure, vous pouvez utiliser les classes ConcurrentHashMap et CopyOnWriteArrayList. C’est l’approche recommandée pour éviter l’exception de modification concurrente.

Pour éviter l’exception de modification concurrente dans un environnement monothréad

Vous pouvez utiliser la fonction remove() de l’itérateur pour supprimer l’objet de l’objet de collection sous-jacent. Mais dans ce cas, vous pouvez supprimer le même objet et aucun autre objet de la liste. Exécutons un exemple en utilisant des classes de collection concurrentes.

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());
	}

}

La sortie du programme ci-dessus est présentée ci-dessous. Vous pouvez voir qu’aucune exception de modification concurrente n’est levée par le programme.

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

De l’exemple ci-dessus, il est clair que:

  1. Les classes de collection concurrentes peuvent être modifiées en toute sécurité, elles ne lèveront pas d’exception de modification concurrente.

  2. Dans le cas de CopyOnWriteArrayList, l’itérateur n’accepte pas les modifications apportées à la liste et fonctionne sur la liste d’origine.

  3. En cas de ConcurrentHashMap, le comportement n’est pas toujours le même. Pour la condition :

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

    La sortie est :

    Valeur de la carte : 1
    Valeur de la carte : null
    Valeur de la carte : 4
    Valeur de la carte : 2
    Taille de la carte : 4
    

    Il prend le nouvel objet ajouté avec la clé « 4 » mais pas l’objet ajouté ensuite avec la clé « 5 ». Maintenant, si je change la condition comme suit :

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

    La sortie est :

    Valeur de la carte : 1
    Valeur de la carte : 3
    Valeur de la carte : null
    Taille de la carte : 4
    

    Dans ce cas, il ne prend pas en compte les nouveaux objets ajoutés. Donc, si vous utilisez ConcurrentHashMap, évitez d’ajouter de nouveaux objets car ils peuvent être traités en fonction de l’ensemble de clés. Notez que le même programme peut imprimer différentes valeurs dans votre système car l’ensemble de clés HashMap n’est pas ordonné.

Utilisez une boucle for pour éviter java.util.ConcurrentModificationException

Si vous travaillez dans un environnement monofile et que vous souhaitez que votre code prenne en charge les objets ajoutés supplémentaires dans la liste, vous pouvez le faire en utilisant une boucle for plutôt qu’un 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");
	}
}

Notez que je diminue le compteur car je supprime le même objet, si vous devez supprimer l’objet suivant ou plus loin, vous n’avez pas besoin de diminuer le compteur. Essayez par vous-même. 🙂 Une autre chose: Vous obtiendrez une ConcurrentModificationException si vous essayez de modifier la structure de la liste d’origine avec subList. Voyons cela avec un exemple simple.

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");
		// Vérifiez la sortie ci-dessous. :)
		System.out.println(names + " , " + first2Names);

		// Modifions la taille de la liste et obtenons une ConcurrentModificationException
		names.add("NodeJS");
		System.out.println(names + " , " + first2Names); // this line throws exception

	}

}

La sortie du programme ci-dessus est:

[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)

Selon la documentation de ArrayList sur subList, les modifications structurelles ne sont autorisées que sur la liste renvoyée par la méthode subList. Toutes les méthodes sur la liste renvoyée vérifient d’abord si le modCount réel de la liste de support est égal à sa valeur attendue et lancent une ConcurrentModificationException si ce n’est pas le cas.

Vous pouvez télécharger tout le code exemple depuis notre Dépôt GitHub .

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