java

[Java] ConcurrentModificationException

matdulgi 2021. 11. 5. 00:45

직역하면 '동시 수정 예외' 라는 뜻으로 일반적으로 Iterator 의 동작 중에 무턱대고 컬렉션의 요소를 더하거나 수정할 때 발생되는 예외이다 >Iterator 동작 원리

왜 동시 수정 예외이고 하니, 한 스레드에서 컬렉션 객체를 순회 중일 때 동시에 다른 스레드에서 해당 컬렉션 객체를 수정할 수 있기 때문이다.

하지만 이름과는 달리 싱글 스레드 작업 도중에도 해당 예외는 발생한다!

다음 코드를 보겠다

ArrayList arrayList = new ArrayList();
    //10개 요소 삽입
    for (int i = 1; i < 10; i++) {
        arrayList.add(i);
        copyOnWriteArrayList.add(i);
    }
    Iterator itr = arrayList.iterator();

try {
    int index = 0;
    while (itr1.hasNext()) {
        Object temp = itr1.next();
        System.out.println("index : " + index + " value : "+temp);
        //index가 3일 경우 해당 인덱스 값 삭제
        if (index == 3) {
            arrayList.remove(index);
        }
        index++;
    }
} catch (ConcurrentModificationException e) {
    System.out.println("도중 인덱스가 변경되었습니다");
}

결과

index : 0 value : 1
index : 1 value : 2
index : 2 value : 3
index : 3 value : 4
도중 인덱스가 변경되었습니다

우리의 예상과는 달리 3번 인덱스가 삭제되는 것이 아닌 예외가 발생하였다!

 

어떤 이유때문에 발생하는가?

Iterator의 next나 remove 등 메소드는 실행시 특정 값을 체크하는 로직이 존재한다

바로 컬렉션의 modCount와 Iterator의 expectedModCount라는 두 변수인데, 정상적인 상황이라면 두 변수는 같아야 한다.

바로 이 두 값을 매번 비교해 주어서 만약 값이 다를 시 ConcurrentModificationException 예외를 발생시킨다.

Iterator의 생성시에 expectedModCount의 값에 modCount값이 저장되고, add나 remove로 인해 컬렉션이 수정되면 증가한 modCount의 값이 저장되면서 항상 두 값을 맞추기 때문이다.

만약 두 값이 다르다는 것은 iterator가 생성된 후에 컬렉션에 자료가 추가되거나 제거되었다는 것이고, 

다른 스레드와 간섭이 되었거나, 싱글 스레드에서 실수로 반복자 생성 후 컬렉션 정보를 수정되었을 경우이다.

즉, 특정 컬렉션으로부터 Iterator가 생성된 후에 해당 컬렉션이 수정되면, 그 Iterator는 사용할 수 없다.  

 

 

그럼 도중에 수정하는 방법은 없을까?

Iterate도중 인덱스를 저장해 두었다가, 끝난 후에 조작하던지

인덱스 정보를 저장해 두고 복사하는 방식의 CopyOnWriteArrayList를 사용하면 된다

	System.out.println("copyOnWriteArray Iterate Test");
		try {
			int index = 0;
			while (itr2.hasNext()) {
				Object temp = itr2.next();
				System.out.println("index : " + index + " value : "+temp);

				if (index == 3) {
					copyOnWriteArrayList.remove(3);
					System.out.println(index + "번 값을 삭제합니다");;
				}

				index++;
			}
		} catch (ConcurrentModificationException e) {
			System.out.println("도중 인덱스가 변경되었습니다");
		}
copyOnWriteArray Iterate Test
index : 0 value : 1
index : 1 value : 2
index : 2 value : 3
index : 3 value : 4
3번 값을 삭제합니다
index : 4 value : 5

예외가 발생하지 않고 계속 실행된다