Iterators Fail-Fast versus Fail-Safe
Compreenda ConcurrentModificationException, o comportamento fail-fast e quando usar iterators baseados em cópias instantâneas.
Iterators Fail-Fast versus Fail-Safe é uma aula grátis de Java Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Java Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Java Academy inclui 4 aulas no total.
Iteradores de falha rápida e de falha segura
Os iteradores de Java são classificados pelo comportamento quando a coleção subjacente é modificada durante a iteração: iteradores de falha rápida lançam uma exceção imediatamente; iteradores de falha segura continuam usando um instantâneo.
Iteradores de falha rápida
A maioria das coleções padrão de Java (ArrayList, HashMap, TreeSet) usa iteradores de falha rápida. Eles controlam um modCount, um contador de mutações. Qualquer modificação estrutural durante a iteração dispara ConcurrentModificationException.
import java.util.*;
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
try {
for (String s : list) {
list.add("x"); // structural modification — throws!
}
} catch (ConcurrentModificationException e) {
System.out.println("ConcurrentModificationException caught!");
}
// Same for HashMap, TreeMap, HashSet, etc.Por que existe a falha rápida
O comportamento de falha rápida ajuda na depuração: ele revela os erros imediatamente, em vez de permitir que a iteração continue com um estado corrompido. A alternativa, uma corrupção silenciosa, é muito mais difícil de depurar.
// modCount is incremented on every structural change
// (add, remove, clear on ArrayList)
// Iterator records modCount on creation
// On each next(), Iterator checks: if current modCount != expected, throw CME
// This detects bugs like:
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5));
for (Integer n : nums) {
if (n == 3) nums.remove(n); // bug caught immediately
}
// ConcurrentModificationException — not silent wrong resultsRemoção segura: Iterator.remove()
A única modificação segura durante uma iteração baseada em Iterator é Iterator.remove(): ela remove o último elemento retornado e atualiza modCount.
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5,6));
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
int n = it.next();
if (n % 2 == 0) it.remove(); // safe: updates modCount
}
System.out.println(nums); // [1, 3, 5]removeIf: alternativa moderna
Java 8 adicionou removeIf(Predicate) a Collection: uma maneira mais simples de remover elementos que correspondem a uma condição, sem gerenciar o iterador manualmente.
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Ann", "Charlie"));
// Modern: removeIf handles iteration internally
names.removeIf(name -> name.startsWith("A"));
System.out.println(names); // [Bob, Charlie]
// Equivalent but verbose iterator approach:
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().startsWith("A")) it.remove();
}Iteradores de falha segura: CopyOnWriteArrayList
CopyOnWriteArrayList usa um iterador de falha segura. Ele percorre um instantâneo criado no momento em que o iterador é criado; as modificações não afetam a iteração em andamento.
import java.util.concurrent.CopyOnWriteArrayList;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.addAll(List.of("a", "b", "c"));
for (String s : list) {
list.add("x"); // no ConcurrentModificationException!
System.out.print(s + " "); // prints a, b, c (snapshot)
}
System.out.println();
System.out.println(list); // [a, b, c, x, x, x] — modified copyIterador de ConcurrentHashMap
ConcurrentHashMap usa iteradores fracamente consistentes: eles refletem o estado em algum momento durante ou depois da criação do iterador, não lançam CME e podem ou não observar atualizações concorrentes.
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1); map.put("b", 2); map.put("c", 3);
for (Map.Entry<String, Integer> e : map.entrySet()) {
map.put("d", 4); // no CME — weakly consistent
System.out.println(e.getKey() + "=" + e.getValue());
}
// May or may not print "d=4" — weakly consistentCollections.synchronizedList
Collections.synchronizedList NÃO é de falha segura: ela ainda usa iteradores de falha rápida, mas bloqueia o acesso concorrente. É necessário sincronizar manualmente durante a iteração.
import java.util.*;
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.addAll(List.of("a", "b", "c"));
// Must synchronize during iteration!
synchronized (syncList) {
for (String s : syncList) {
System.out.println(s);
}
}
// Without the synchronized block, CME is still possible from another threadPadrão de iterador de instantâneo
Crie manualmente um instantâneo para percorrer com segurança uma coleção mutável sem sincronização.
import java.util.*;
List<String> original = new ArrayList<>(List.of("a", "b", "c"));
// Take a snapshot copy before iterating
List<String> snapshot = List.copyOf(original);
for (String s : snapshot) {
// Safe to modify original during snapshot iteration
original.remove(s);
System.out.println("Removed: " + s);
}
System.out.println(original); // []Compromissos de CopyOnWrite
CopyOnWriteArrayList é de falha segura, mas tem um custo alto para gravações. Cada mutação cria uma cópia completa.
- Bom para: muitas leituras e gravações raras, como listas de ouvintes de eventos
- Ruim para: mutações frequentes e coleções grandes
Resumo: falha rápida e falha segura
Resumo da comparação:
- Falha rápida: ArrayList, HashMap, TreeMap — lançam CME quando há modificação e verificam modCount
- Falha segura/fracamente consistente: ConcurrentHashMap, CopyOnWriteArrayList — não lançam CME e percorrem um instantâneo ou dados concorrentes
- Métodos de modificação segura: Iterator.remove(), removeIf(), replaceAll()
Prática: limpeza de sessões expiradas
Remova com segurança sessões expiradas de uma lista concorrente usando padrões apropriados.
import java.util.*;
import java.util.concurrent.*;
class SessionManager {
private final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>();
record Session(String id, long expiresAt) {
boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
}
void add(Session s) { sessions.add(s); }
// Safe to call from multiple threads while iterating
void purgeExpired() {
sessions.removeIf(Session::isExpired);
}
List<Session> active() {
return sessions.stream().filter(s -> !s.isExpired()).toList();
}
}Verificação rápida
Qual exceção é lançada ao modificar um ArrayList durante a iteração?
Recapitulação: iteradores de falha rápida e de falha segura
Principais aprendizados:
- Iteradores de falha rápida (ArrayList, HashMap) lançam ConcurrentModificationException quando ocorre uma modificação concorrente
- Iteradores de falha segura (CopyOnWriteArrayList, ConcurrentHashMap) permitem modificações sem lançar exceções
- Iterator.remove() é a única maneira segura de remover elementos durante uma iteração de falha rápida
- removeIf() é a alternativa moderna e mais simples ao uso manual de Iterator.remove()
- CopyOnWriteArrayList: seguro em cenários com muitas leituras e poucas gravações
- ConcurrentHashMap: fracamente consistente — não lança CME e pode ou não observar modificações concorrentes
Perguntas Frequentes
A aula “Iterators Fail-Fast versus Fail-Safe” é grátis?
Sim — o texto completo de “Iterators Fail-Fast versus Fail-Safe” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Java Academy, atualize para CoddyKit PRO. O curso de Java Academy inclui 4 aulas no total.
O que vou aprender em “Iterators Fail-Fast versus Fail-Safe”?
Compreenda ConcurrentModificationException, o comportamento fail-fast e quando usar iterators baseados em cópias instantâneas. Você pratica Java Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Java Academy?
Nenhuma experiência prévia é necessária. Java Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Iterators Fail-Fast versus Fail-Safe”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Java Academy?
Sim. Cada aula de Java Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Contratos de Iterable e Iterator
- Implementação de um Iterator Personalizado
- ListIterator e Percurso Bidirecional
- Iterators Fail-Fast versus Fail-Safe