Study/java

[Java] Collection - ConcurrentModificationException: toString

 

리스트의 remove 메소드를 사용하지 않았는데 ConcurrentModificationException이 발생하는 경우가 있습니다.

 

[Java] Collection - ConcurrentModificationException

보통은 컬렉션의 remove 메소드를 사용했을 때 해당 예외가 자주 발생합니다.

최근에 다른 상황에서 해당 예외가 발생했어서 이에 대해 정리해보려 합니다.

 

toString()

컬렉션을 문자열로 변환할 때 예외가 발생할 수 있습니다.

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

Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 200; i++) {
        list.add(String.valueOf(i));
    }
});

Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println(list.toString());
    }
});
thread1.start();
thread2.start();

toString에서 ConcurrentModificationException 발생

코드를 설명하자면, 첫번째 스레드에서 리스트에 값을 계속 추가하는 도중에 두번째 스레드에서 리스트를 문자열로 변환하는 코드입니다.

리스트 내부의 값이 계속 추가되고있었기 때문에 toString을 사용할 때 리스트에 변화가 감지되어 예외가 발생하였습니다.

물론 동시성을 처리할 수 있는 리스트를 사용하면 해당 예외는 발생하지 않습니다.

 

 

 

 

업무를 하는 중에 이 상황을 만나게 되었습니다.

컬렉션에 remove를 한 적이 없는데 해당 예외가 발생해서 원인을 찾다가 AbstractCollection 클래스에 있는 toString 메소드에서 iterator를 사용하는 것을 발견하였습니다.

// AbstractCollection.java:448
public String toString() {
        Iterator<E> it = iterator();
....

 

toString 외에도 AbstractCollection 클래스의 contains, clear와 같은 메소드들이 내부에서 iterator를 사용하고 있습니다.

하지만 AbstractCollection 클래스를 상속받은 대부분의 구현체들은 해당 메소드들을 오버라이딩하여 사용하고 있습니다.

그래서 AbstractCollection의 메소드를 직접 사용하는 경우가 거의 없습니다.

 

위의 코드와 같은 상황도, 처음보는 상황이라 신기해서 좀 더 조사해보게 되었습니다.

예외의 원인 자체는 이전 글에서 설명했듯이 iterator를 사용했기 때문입니다.

하지만 일반적으로 알고있는 원인인 remove 메소드를 사용하는 상황이 아니었기도 했고,

iterator를 쓰지 않을 것 같은 메소드에서 사용하고 있었기 때문에 이에 대해 정리하고 싶었습니다.