java.util.ConcurrentModificationException é uma exceção muito comum ao trabalhar com classes de coleção Java. As classes de coleção Java são fail-fast, o que significa que se a coleção for modificada enquanto algum thread estiver percorrendo-a usando um iterador, o iterator.next()
lançará ConcurrentModificationException. A exceção de modificação concorrente pode ocorrer tanto em ambientes de programação Java multithread quanto em ambientes de programação Java single-threaded.
java.util.ConcurrentModificationException
Vamos ver o cenário de exceção de modificação concorrente com um exemplo.
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");
}
}
}
}
O programa acima lançará java.util.ConcurrentModificationException
quando executado, como mostrado nos logs do console abaixo.
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)
A partir do rastreamento de pilha de saída, é claro que a exceção de modificação concorrente é lançada quando chamamos a função next()
do iterador. Se você está se perguntando como o Iterator verifica a modificação, sua implementação está presente na classe AbstractList, onde uma variável int modCount é definida. O valor de modCount é usado em cada chamada de next() para verificar quaisquer modificações na função checkForComodification()
. Agora, comente a parte da lista e execute o programa novamente. Você verá que nenhuma ConcurrentModificationException é lançada agora. Saída:
Map Value:3
Map Value:2
Map Value:4
Como estamos atualizando o valor da chave existente no myMap, seu tamanho não foi alterado e não estamos obtendo ConcurrentModificationException. A saída pode ser diferente em seu sistema porque HashMap keyset não é ordenado como uma lista. Se descomentar a instrução onde estou adicionando um novo par chave-valor no HashMap, isso causará uma ConcurrentModificationException.
Para evitar ConcurrentModificationException em um ambiente multithread
- Você pode converter a lista para um array e depois iterar sobre o array. Essa abordagem funciona bem para listas pequenas ou médias, mas se a lista for grande, afetará muito o desempenho.
- Você pode bloquear a lista enquanto itera colocando-a em um bloco sincronizado. Esta abordagem não é recomendada porque cessará os benefícios da multithreading.
- Se você estiver usando JDK1.5 ou superior, então você pode usar as classes ConcurrentHashMap e CopyOnWriteArrayList. Esta é a abordagem recomendada para evitar a exceção de modificação concorrente.
Para evitar ConcurrentModificationException em um ambiente com um único thread
Você pode usar a função remove()
do iterador para remover o objeto do objeto de coleção subjacente. Mas, neste caso, você pode remover o mesmo objeto e não qualquer outro objeto da lista. Vamos executar um exemplo usando classes de Coleção Concorrente.
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());
}
}
A saída do programa acima é mostrada abaixo. Você pode ver que não há ConcurrentModificationException sendo lançada pelo programa.
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
A partir do exemplo acima, está claro que:
-
As classes de Coleção Concorrente podem ser modificadas com segurança, elas não lançarão ConcurrentModificationException.
-
No caso de CopyOnWriteArrayList, o iterador não acomoda as alterações na lista e funciona na lista original.
-
No caso do ConcurrentHashMap, o comportamento nem sempre é o mesmo. Para a condição:
if(key.equals("1")){ myMap.remove("3");}
A saída é:
Mapa Valor:1 Mapa Valor:null Mapa Valor:4 Mapa Valor:2 Tamanho do Mapa:4
Ele está pegando o novo objeto adicionado com a chave “4”, mas não o próximo objeto adicionado com a chave “5”. Agora, se eu mudar a condição para o seguinte:
if(key.equals("3")){ myMap.remove("2");}
A saída é:
Mapa Valor:1 Mapa Valor:3 Mapa Valor:null Tamanho do Mapa:4
Neste caso, ele não está considerando os objetos recém-adicionados. Portanto, se você estiver usando ConcurrentHashMap, evite adicionar novos objetos, pois eles podem ser processados dependendo do conjunto de chaves. Observe que o mesmo programa pode imprimir valores diferentes em seu sistema, pois o conjunto de chaves do HashMap não é ordenado.
Use for loop to avoid java.util.ConcurrentModificationException
Se estiver a trabalhar num ambiente de uma única thread e deseja que o seu código cuide dos objetos adicionados extra na lista, pode fazê-lo utilizando um loop em vez de um 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");
}
}
Observe que estou a diminuir o contador porque estou a remover o mesmo objeto. Se precisar de remover o próximo ou um objeto mais distante, não é necessário diminuir o contador. Experimente por si mesmo. 🙂 Mais uma coisa: Você receberá ConcurrentModificationException se tentar modificar a estrutura da lista original com subList. Vamos ver isso com um exemplo simples.
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");
// Verifique a saída abaixo. :)
System.out.println(names + " , " + first2Names);
// Vamos modificar o tamanho da lista e obter ConcurrentModificationException
names.add("NodeJS");
System.out.println(names + " , " + first2Names); // this line throws exception
}
}
A saída do programa acima é:
[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)
De acordo com a documentação de subList da ArrayList, as modificações estruturais são permitidas apenas na lista retornada pelo método subList. Todos os métodos na lista retornada primeiro verificam se o modCount real da lista de suporte é igual ao seu valor esperado e lançam uma ConcurrentModificationException se não for.
Pode baixar todo o código de exemplo do nosso Repositório do GitHub.
Source:
https://www.digitalocean.com/community/tutorials/java-util-concurrentmodificationexception