본문 바로가기
Old Posts/Java

[Java] ConcurrentModificationException 원인과 해결방법

by A6K 2022. 5. 26.

자바 언어로 프로그래밍을 하다보면 가끔씩 ConcurrentModificationException을 만나게 된다. ConcurrentModificationException의 발생 원인과 해결방법에 대해서 간단하게 소개하겠다.

ConcurrentModificationException

ConcurrentModificationException은 몇 가지 상황에서 발생할 수 있는데, 가장 흔한 것이 컬렉션을 순회하면서 순회하는 대상 컬렉션에 수정을 가하는 경우다.

다음 코드를 실행하면 ConcurrentModificationException이 발생한다.

List<String> list = new ArrayList<>();

list.add("str1");
list.add("str2");
list.add("str3");

for (String str : list) {
	if ("str1".equals(str)) {
		list.remove(str);
	}
}

단순하게 생각하면 아무 문제 없는 소스코드로 보인다. 컬렉션을 순회하면서 특정 값이 있는 엘리먼트를 검출해 삭제하는 간단한 코드다.

컬렉션 객체에는 엘리먼트가 추가되거나 제거될 때마다 modCount 변수를 수정하는 동작이 추가되어 있다. 동시에 컬렉션 체를 순회하는 Iterator 클래스의 next() 메소드에는 순회가 시작될 때 컬렉션의 modCount와 현재 modCount를 비교하는 동작이 있다. 이 때, 두 값이 다르면 ConcurrentModificationException이 발생한다.

비슷한 원리로 두 스레드가 동시에 컬렉션을 접근하는 과정에서 한쪽이 순회하고 다른 한쪽이 수정하는 케이스에서도 발생할 수 있다.

final List<String> list = new ArrayList<>();

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        while(true) {
            list.add("ABC");
        }
    }
});


Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        while(true) {
            for (String str : list) {
                System.out.println(str);
            }
        }
    }
});

t1.start();
t2.start();

t1 스레드는 리스트에 값을 추가하고 t2 스레드는 리스트를 순회한다. 반복적으로 수행하다보면 결국 ConcurrentModificationException이 발생한다.

해결방법

첫 번째 케이스에서는 컬렉션에 직접 수정을 하지말고 iterator를 통해 삭제하면 된다.

List<String> list = new ArrayList<>();

list.add("str1");
list.add("str2");
list.add("str3");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    if ("str1".equals(str)) {
        iterator.remove();
    }
}

ConcurrentModificationException이 발생하지 않는다.

멀티 스레드 환경에서 발생하는 케이스는 ArrayList 대신 java.util.concurrent 패키지에 정의된 클래스를 사용하면 된다.

댓글