java.util.ConcurrentModificationException é uma exceção muito comum ao trabalhar com classes de coleção em Java. As classes de coleção em Java são de detecção rápida (fail-fast), o que significa que se a coleção for alterada 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 de thread único.
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 da pilha de rastreamento 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 modCount fornece o número de vezes que o tamanho da lista foi alterado. O valor modCount é usado em cada chamada de next() para verificar se houve modificações na função checkForComodification()
. Agora, comente a parte da lista e execute o programa novamente. Você verá que não está sendo lançada nenhuma ConcurrentModificationException agora. Saída:
Map Value:3
Map Value:2
Map Value:4
Como estamos atualizando o valor da chave existente em myMap, seu tamanho não foi alterado e não estamos recebendo ConcurrentModificationException. A saída pode ser diferente em seu sistema porque o keyset do HashMap não está ordenado como uma Lista. Se você descomentar a declaração onde estou adicionando um novo par chave-valor no HashMap, isso causará ConcurrentModificationException.
Para evitar ConcurrentModificationException em um ambiente multithread
- Você pode converter a lista em um array e então iterar sobre o array. Essa abordagem funciona bem para listas pequenas ou médias, mas se a lista for grande, isso afetará muito o desempenho.
- Você pode travar a lista enquanto itera colocando-a em um bloco sincronizado. Essa abordagem não é recomendada porque ela anula 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 ambiente de thread único
Você pode usar a função remove()
do iterator 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());
}
}
O resultado do programa acima é mostrado 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 fica 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 de ConcurrentHashMap, o comportamento nem sempre é o mesmo. Para a condição:
if(key.equals("1")){ myMap.remove("3");}
A saída é:
Valor do Mapa: 1 Valor do Mapa: null Valor do Mapa: 4 Valor do Mapa: 2 Tamanho do Mapa: 4
Ele está considerando 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 é:
Valor do Mapa: 1 Valor do Mapa: 3 Valor do Mapa: 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. Note que o mesmo programa pode imprimir valores diferentes em seu sistema porque o conjunto de chaves do HashMap não é ordenado.
Use o loop for para evitar java.util.ConcurrentModificationException
Se estiver trabalhando em um ambiente de thread único e quiser que seu código cuide dos objetos adicionados extras na lista, você pode fazer isso usando um loop for 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 diminuindo o contador porque estou removendo o mesmo objeto. Se precisar remover o próximo objeto ou um objeto ainda 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 de 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.
Você pode baixar todo o código de exemplo do nosso Repositório do GitHub.
Source:
https://www.digitalocean.com/community/tutorials/java-util-concurrentmodificationexception