컬렉션(Collection) 객체를 다룰 때 한 스레드가 특정 컬렉션을 순회(iteration)하는 도중에 해당 컬렉션에 요소를 추가하거나 삭제하면 ConcurrentModificationException이 발생합니다.
뿐만 아니라, 컬렉션을 순회하는 도중에 요소를 추가·삭제한 뒤 기존 Iterator 객체로 다시 내용을 읽으려고 시도하면, 여러 스레드가 동시에 컬렉션에 접근하려는 것으로 간주되어 역시 같은 예외가 발생합니다. 이는 Java 컬렉션이 fail-fast(즉시 실패) 방식으로 동작하기 때문인데, 내부적으로 구조 변경 횟수(modCount)를 추적하다가 불일치가 감지되면 즉시 예외를 던집니다.
예외 발생 예시
import java.util.ArrayList;
import java.util.Iterator;
public class OccurenceOfElements {
public static void main(String args[]) {
ArrayList <String> list = new ArrayList<String>();
// ArrayList 객체 생성
list.add("JavaFX");
list.add("Java");
list.add("WebGL");
list.add("OpenCV");
System.out.println("배열 리스트의 내용 (처음부터 끝까지): ");
Iterator<String> it = list.iterator();
while(it.hasNext()) {
System.out.print(it.next()+", ");
}
//list.remove(3);
list.add(3, "Hadoop");
while(it.hasNext()) {
System.out.print(it.next()+", ");
}
}
}실행 결과
배열 리스트의 내용 (처음부터 끝까지):
JavaFX, Java, WebGL, OpenCV, Exception in thread "main"
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at sample.OccurenceOfElements.main(OccurenceOfElements.java:23)위 예제에서는 첫 번째 순회가 끝난 후 리스트에 요소를 추가하고, 같은 Iterator 객체로 다시 순회를 시도했기 때문에 예외가 발생했습니다.
해결 방법
1. 수정 후 새로운 Iterator 객체 다시 얻기
여러 스레드에서 컬렉션 객체에 접근해야 하는 경우에는 synchronized 블록이나 synchronized 메서드를 사용해 접근을 동기화해야 합니다. 또한, 데이터를 조회하는 도중에 수정이 필요하다면 수정 작업을 마친 후 새로운 Iterator 객체를 다시 얻어 순회를 시작해야 합니다.
예시 코드
import java.util.ArrayList;
import java.util.Iterator;
public class OccurenceOfElements {
public static void main(String args[]) {
ArrayList <String> list = new ArrayList<String>();
// ArrayList 객체 생성
list.add("JavaFX");
list.add("Java");
list.add("WebGL");
list.add("OpenCV");
System.out.println("배열 리스트의 내용 (처음부터 끝까지): ");
Iterator<String> it = list.iterator();
while(it.hasNext()) {
System.out.print(it.next()+". ");
}
list.remove(3);
System.out.println("");
System.out.println("삭제 후 배열 리스트의 내용: ");
// 수정 후 새로운 Iterator 객체를 다시 얻음
it = list.iterator();
while(it.hasNext()) {
System.out.print(it.next()+". ");
}
}
}실행 결과
배열 리스트의 내용 (처음부터 끝까지): JavaFX. Java. WebGL. OpenCV. 삭제 후 배열 리스트의 내용: JavaFX. Java. WebGL.
2. Iterator의 remove() 메서드 활용
순회 중에 요소를 삭제해야 한다면 컬렉션의 remove() 대신 Iterator가 제공하는 remove() 메서드를 사용하면 안전하게 삭제할 수 있습니다. 이 방식은 내부 카운터를 함께 갱신하기 때문에 예외가 발생하지 않습니다.
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String element = it.next();
if(element.equals("OpenCV")) {
it.remove(); // 안전한 삭제
}
}3. 동시성 지원 컬렉션 사용
멀티스레드 환경이라면 CopyOnWriteArrayList, ConcurrentHashMap과 같은 java.util.concurrent 패키지의 동시성 컬렉션을 사용하는 것도 좋은 대안입니다. 이러한 컬렉션은 순회 중 수정이 발생해도 ConcurrentModificationException을 던지지 않도록 설계되어 있습니다.
정리
- 순회 중 컬렉션 구조를 직접 수정하면 ConcurrentModificationException이 발생한다.
- 수정 후에는 반드시 새로운 Iterator 객체를 다시 얻어 사용한다.
- 순회 중 삭제가 필요하면 Iterator의 remove() 메서드를 활용한다.
- 멀티스레드 환경에서는 synchronized 동기화 또는 동시성 지원 컬렉션(CopyOnWriteArrayList 등)을 사용한다.