ConcurrentModificationException
Vấn đề
Phần tiêu đề “Vấn đề”Thêm hoặc xóa phần tử của một List/Map/Set trong khi đang duyệt nó bằng vòng lặp for-each sẽ ném ConcurrentModificationException - ngay cả khi chương trình chỉ chạy trên một luồng duy nhất (không liên quan gì đến đa luồng, dù tên gọi có chữ “Concurrent”):
import java.util.ArrayList;import java.util.List;
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
for (int n : numbers) { if (n % 2 == 0) { numbers.remove(Integer.valueOf(n)); // NGUY HIỂM! Sửa list trong khi đang duyệt }}// ConcurrentModificationException!Vì sao xảy ra?
Phần tiêu đề “Vì sao xảy ra?”for-each bên trong sử dụng một Iterator để duyệt qua danh sách. Iterator theo dõi một bộ đếm “số lần sửa đổi” (modCount) của danh sách - nếu danh sách bị sửa đổi trực tiếp (không qua chính Iterator đó) trong khi đang duyệt, Iterator phát hiện sự bất nhất và chủ động ném exception để tránh hành vi không xác định (ví dụ bỏ sót hoặc duyệt lặp một phần tử do chỉ số bị dịch chuyển khi xóa).
Giải pháp
Phần tiêu đề “Giải pháp”1. Dùng Iterator tường minh và gọi iterator.remove() thay vì list.remove():
import java.util.Iterator;
Iterator<Integer> iterator = numbers.iterator();while (iterator.hasNext()) { int n = iterator.next(); if (n % 2 == 0) { iterator.remove(); // AN TOÀN - xóa thông qua chính Iterator, nó tự cập nhật trạng thái }}2. Dùng removeIf() (Java 8+) - cách gọn và an toàn nhất:
numbers.removeIf(n -> n % 2 == 0); // xóa mọi phần tử thỏa điều kiện, không cần vòng lặp thủ công3. Duyệt trên một bản sao nếu cần sửa danh sách gốc theo cách phức tạp hơn:
List<Integer> copy = new ArrayList<>(numbers);for (int n : copy) { // duyệt bản SAO if (n % 2 == 0) { numbers.remove(Integer.valueOf(n)); // sửa danh sách GỐC - an toàn vì không duyệt chính nó }}Tóm tắt
Phần tiêu đề “Tóm tắt”ConcurrentModificationException: xảy ra khi sửa đổi trực tiếp một collection trong khi đang duyệt nó bằng
for-each/Iterator - kể cả trên một luồng duy nhất.
- Ưu tiên
removeIf()cho trường hợp xóa theo điều kiện - đơn giản và an toàn nhất- Dùng
iterator.remove()khi cần logic phức tạp hơn trong lúc duyệt- Không bao giờ gọi
list.add()/list.remove()trực tiếp trong thân vòng lặpfor-eachđang duyệt chínhlistđó