ConcurrentModificationException(동시 수정 오류)란 무엇인가?
1. ConcurrentModificationException이란?
ConcurrentModificationException은 Java에서 컬렉션(Collection) 객체를 반복(iterate)하는 동안 해당 컬렉션이 다른 곳에서 수정되면 발생하는 런타임 예외입니다. 이 예외는 보통 하나의 스레드에서 컬렉션을 반복하면서 동시에 그 컬렉션을 수정할 때 발생합니다. Java의 컬렉션 클래스(List, Set, Map 등)는 기본적으로 한 번에 하나의 스레드만 안전하게 수정할 수 있도록 설계되어 있기 때문에, 동시 수정이 발생하면 ConcurrentModificationException
이 발생합니다.
2. ConcurrentModificationException이 발생하는 이유
이 예외는 주로 다음과 같은 상황에서 발생합니다:
- 반복 중 컬렉션 수정: 컬렉션 객체를 반복하는 동안
add()
,remove()
메소드를 사용해 컬렉션의 요소를 추가하거나 삭제할 때 발생합니다. - 멀티스레딩 환경에서의 동시 수정: 두 개 이상의 스레드가 동일한 컬렉션을 동시에 수정하려고 할 때 발생할 수 있습니다.
3. ConcurrentModificationException 예시
3.1 기본적인 예시
아래 예시는 리스트를 반복하는 동안 리스트를 수정하려 할 때 발생하는 ConcurrentModificationException
입니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ConcurrentModificationException 발생
}
}
}
}
위 코드에서는 리스트를 반복하면서 동시에 요소를 삭제하려고 하기 때문에 ConcurrentModificationException
이 발생합니다.
3.2 멀티스레딩 환경에서의 예시
두 개 이상의 스레드가 동일한 리스트를 수정할 때도 이 예외가 발생할 수 있습니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 첫 번째 스레드: 리스트를 반복
new Thread(() -> {
for (int item : list) {
System.out.println(item);
}
}).start();
// 두 번째 스레드: 리스트 수정
new Thread(() -> {
list.add(10); // ConcurrentModificationException 발생 가능
}).start();
}
}
이 코드에서는 두 개의 스레드가 동시에 리스트를 반복 및 수정하면서 ConcurrentModificationException
이 발생할 수 있습니다.
4. ConcurrentModificationException을 해결하는 방법
이 예외를 방지하기 위해 몇 가지 방법을 사용할 수 있습니다:
- Iterator 사용 시 remove() 메소드 사용: 반복문 안에서 요소를 제거할 때는 컬렉션의
remove()
대신Iterator
의remove()
메소드를 사용해야 합니다. - CopyOnWriteArrayList 사용:
CopyOnWriteArrayList
와 같은 스레드 안전한 컬렉션을 사용하여, 동시 수정이 발생해도 예외가 발생하지 않도록 할 수 있습니다. 이 컬렉션은 수정 시 새로운 배열을 생성하여 안전한 동시성을 제공합니다. - 컬렉션 동기화:
Collections.synchronizedList()
를 사용하여 리스트를 동기화할 수 있습니다. 동기화된 컬렉션은 여러 스레드에서 안전하게 접근 및 수정할 수 있습니다.
4.1 Iterator의 remove() 메소드 사용
아래는 Iterator
의 remove()
메소드를 사용하여 ConcurrentModificationException
을 방지하는 예시입니다:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // 안전한 삭제
}
}
}
}
이 코드는 Iterator
의 remove()
메소드를 사용하여 안전하게 리스트에서 요소를 삭제할 수 있도록 합니다.
4.2 CopyOnWriteArrayList 사용
다음은 CopyOnWriteArrayList
를 사용하여 동시 수정 예외를 방지하는 예시입니다:
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
public static void main(String[] args) {
List list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // 안전한 삭제
}
}
}
}
CopyOnWriteArrayList
는 컬렉션을 수정할 때 내부적으로 새로운 배열을 생성하여 동시 수정이 발생하지 않도록 설계된 클래스입니다.
5. 컬렉션 동기화
멀티스레드 환경에서 컬렉션을 수정하려면 동기화된 컬렉션을 사용하는 것이 좋습니다. 아래는 Collections.synchronizedList()
를 사용한 예시입니다:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList<>());
// 스레드 1: 리스트 수정
new Thread(() -> {
list.add("A");
}).start();
// 스레드 2: 리스트 반복
new Thread(() -> {
synchronized (list) {
for (String item : list) {
System.out.println(item);
}
}
}).start();
}
}
Collections.synchronizedList()
는 동기화된 리스트를 제공하여 여러 스레드가 안전하게 리스트에 접근할 수 있도록 해줍니다.
6. 결론
ConcurrentModificationException은 Java에서 컬렉션을 반복하면서 동시에 수정할 때 발생하는 예외입니다. 이 예외를 해결하기 위해서는 Iterator
의 remove()
메소드를 사용하거나, 스레드 안전한 컬렉션을 사용해야 합니다. 또한, 멀티스레드 환경에서는 컬렉션을 동기화하여 예외 발생을 방지할 수 있습니다.