컨텐츠 바로가기

ConcurrentModificationException의 한 예

http://iilii.egloos.com/5350490

List<String> list = new ArrayList<String>();    
list.add("hello");    
list.add("world");
list.add("!!!!!!");     
for (String str : list) {  
    System.out.println(str);
    if(str.equals("hello")) {   
        list.remove(str);
    }   
}

위의 코드는 java.util.ConcurrentModificationException을 발생시킵니다.
적당히 돌면서 삭제하라는데!! 아니 왜?

     for (String str : list) {   

요 구문은 아래 구문의 축약형이라고 보시면 됩니다.

     for(Iterator<String> iter = list.iterator();  iter.hasNext(); ){
      String str = iter.next();

iterator를 가져왔는데, 그 iterator가 바라 보고 있는 원본의 데이터가 변경되었기 때문에 발생하는 것입니다. 코드에서 보면 list.remove()를 호출하지요. 즉, list에 직접 조작을 가한 겁니다.

이와 유사한 문제로 1개의 list에 대해서 2개의 iterator가 돌 때, 그 중 하나가 변경 작업을 하면 반대편에서 에러가 납니다.

for(Iterator<String> iter1 = list.iterator() , iter2 = list.iterator();  iter1.hasNext() && iter2.hasNext(); ){
      String str1 = iter1.next();
      String str2 = iter2.next();
      System.out.println(str1+":"+str2);
      if (str1.equals("hello")) {
        iter1.remove(); // 요기서 iter1에서 삭제.
      }
}

요거는 str2에서 에러가 납니다. iter1을 이용해서 list의 데이터를 조작했기 때문에, iter1에서 변경된 내용은 list에 잘 반영이 되었고, iter2 입장에서는 위에서와 같이 iterator가 도는 중에 원본 list가 수정된 것과 같은 상황이 발생한 겁니다.

외부의 remove와 마찬가지로 add도 좀 이상한 문제를 발생시킬 소지가 있습니다.
따라서 멀티 Thead 환경에서 collection을 이용하는 iterator를 쓸 때는

1. add
2. remove
3. iterator를 이용하는 부분 전체
 
이 세 가지를 하나의 sync 객체를 이용해 줘야 합니다.synchronized 분석은 여기!!



또 재밌는 부분은 list의 element가 2개만 있을 경웁니다.

List<String> list = new ArrayList<String>();    
list.add("hello");    
list.add("world");
//list.add("!!!!!!");     

위와 같이 3번 째 element를 막아놓습니다. 그럼 에러 안 납니다.

그 이유를 알아보기 전에 먼저 지금까지 에러가 난 부분이 어딘지를 살펴 봅시다. 아래에 에러가 나는 라인을 색깔로 표시했습니다.

for (Iterator<String> iter = list.iterator() ;  iter.hasNext() ;) {
   String str = iter.next();
   System.out.println(str);
   if (str.equals("hello")) {
      list.remove(str);
   }


에러 위치는 next()라는 메쏘드입니다.

element가 2개 일 때 에러가 나지 않는 이유는 루프를 처음 돌면, list의 사이즈는 2에서 시작해서 루프 마지막 부분에서 1로 줄어듭니다. 루프가 한 번 끝나고 나면, iter의 현재 위치는 1이 됩니다. (0에서 시작한다고 가정)
두 번째로 루프가 시작하는 시점에서 list의 size는 1이 됩니다. 따라서 iter.hasNext()는 iter의 현재 위치 값(1)이 list의 size() 값(1)과 같기 때문에 false가 됩니다.
따라서 두번째 loop는 실행되지 않고, 빨간 부분으로 다시 들어갈 이유가 없는 것입니다.

이 것은 hasNext 메쏘드의 구현에 의해 생긴 문제입니다.

어쨌건!!! ConcurrentModificationException의 경우는 잘못된 작업(위의 경우는 remove )을 하는 데서 에러가 안 나고 그에 의해 영향을 받은 엄한 데서 에러가 날 수 있기 때문에 조심조심조심해야 합니다!

덧글|덧글 쓰기|신고