티스토리 뷰

List를 조회하며 원하는 객체를 삭제할 때 볼 수 있는 예외입니다. 자바 도큐먼트의 설명을 발췌하면 아래와 같습니다.

 

This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.

한 객체에 대하여 동시 수정이 허가되지 않았음에도, 그러한 경우가 포착되면 발생하는 예외입니다. 이 문장만 보면 싱글 스레드 환경에서 발생하지 않는 예외라고 생각할 수 있지만, 더 읽어보면 아래와 같은 문장을 볼 수 있습니다.

 

For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

fail-fast iterator가 생소한데 이는 예외가 발생할 경우, 즉시 작업을 멈추고 던져주는 iterator를 의미합니다. 자세한 내용은 여기서 확인하세요.

 


이제 ConcurrentModificationException가 발생할 수 있는 상황에 대해서 살펴봅시다.

 

예를들어, 학생 객체를 가지고 있는 ArrayList에서 이름이 Kim인 학생을 제거한다고 가정해 봅시다. 아래와 같이 작성하면 예외가 발생합니다.

public static void main(String[] args) {
    List<Student> list = new ArrayList<>(Arrays.asList(
            new Student("Kim"),
            new Student("Ham"),
            new Student("Kim"),
            new Student("Choi")
    ));

    for (Student student : list) {
        if (student.getName().equals("Kim")) {
            list.remove(student);
        }
    }
}

위와 같은 for 문을 향상된 for문 혹은 foreach문이라 하는데, foreach문 말고 인덱스를 통해서 제거해보겠습니다.

public static void main(String[] args) {
    List<Student> list = new ArrayList<>(Arrays.asList(
            new Student("Kim"),
            new Student("Ham"),
            new Student("Kim"),
            new Student("Choi")
    ));

    for(int i=0;i<list.size();i++) {
        Student student = list.get(i);
        if (student.getName().equals("Kim")) {
            list.remove(student);
        }
    }
}

예외가 발생하지 않아서 정상적으로 동작한 것 같지만, 결과를 직접 출력해 보면 Kim 학생이 리스트에서 제거되지 않은 것을 볼 수 있습니다. 이는 인덱스로 접근하는데, 중간에 리스트의 사이즈가 변해 의도한대로 접근할 수 없었기 때문입니다.

 

인덱스로 접근할 때 예외가 발생하지 않은 것에 대해서는 다음 문장을 보면 알 수 있습니다.

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification.

항상 ConcurrentModificationException이 발생하는 경우를 보장할 수 없다는 것입니다. 따라서 공식 문서에서는 해당 예외에 의존하여 프로그램을 작성하지 말 것을 권장합니다


해결 방법은 새로운 리스트를 사용하는 방법과 Iterator를 사용하는 방법이 있습니다.

 

List<Student> temp = new ArrayList<>(list);

for (Student student : temp) {
    if (student.getName().equals("Kim")) {
        list.remove(student);
    }
}

새로운 ArrayList를 생성하고, 객체를 복사하는 것은 추가적인 메모리가 발생하여 보통 Iterator를 사용한다고 합니다.

 

단, 주의해야할 점이 있습니다.

while (iterator.hasNext()) {
    Student student = iterator.next();
    if (student.getName().equals("Kim")) {
        list.remove(student);
    }
}

이와 같이 작성하면 다시 예외가 발생합니다. 그 이유는 아래와 같습니다.

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. 

따라서 아래와 같이 수정해야합니다.

while (iterator.hasNext()) {
    Student student = iterator.next();
    if (student.getName().equals("Kim")) {
        iterator.remove();
    }
}

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함