본문 바로가기
Develop/Java

[Java] ConcurrentModificationException 에러

by 연로그 2022. 2. 24.
반응형

❗ java.util.ConcurrentModificationException

👉 객체를 동시에 수정하는 것이 불가능한데 동시 수정이 일어나는 경우를 감지

 

위 오류가 발생한 상황은 아래와 같다.

public class Random {
    private static final List<Integer> randomNumbers = new ArrayList<>();

    static {
        for (int i = 1; i <= 45; i++) {
            randomNumbers.add(i);
        }
    }

    public List<Integer> generateRandom() {
        Collections.shuffle(randomNumbers);
        return randomNumbers.subList(0, 6);
    }
    
    // ...
}

 

  1. static 영역에서 randomNumbers를 1부터 45까지 생성
  2. generateRandom()을 호출
    • 1의 List가 shuffle됨
    • List의 index 0부터 5까지를 잘라서 반환

 

여기서 generateRandom()을 통해 return 받아온 List의 요소를 수정할 일이 생겼다.

이 때 발생한 것이 ConcurrentModificationException이다.

 

generateRandom()을 호출하면 결과가 다 다를 것이라 생각했고 다 개별적으로 존재한다생각했다.

@Test
public void test() {
    Random random = new Random();
    List<Integer> randomList1 = random.generateRandomLottoNumbers();
    List<Integer> randomList2 = random.generateRandomLottoNumbers();
    List<Integer> randomList3 = random.generateRandomLottoNumbers();

    System.out.println(randomList1);
    System.out.println(randomList2);
    System.out.println(randomList3);
}

다 같은 결과...

 

static 영역에 있는 List라 뭔가 문제가 있는건가?

하지만 매번 shuffle되니 상관없지 않나?

여러 생각을 하다가 subList에 주목해보았다.

 

List 클래스의 subList 메소드를 열어보면 아래와 같은 설명이 되어있다.

 Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive. (If fromIndex and toIndex are equal, the returned list is empty.) The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa. The returned list supports all of the optional list operations supported by this list.

주목할 점은 subList에 의해 반환된 list는 원본 list가 수정되면 함께 수정된다.

반대로 subList로 반환된 list를 수정해도 원본 list까지 수정된다.

 

깊은 복사와 얕은 복사

 

이를 해결하기 위해 generateRandom()을 아래와 같이 수정하였다.

public List<Integer> generateRandom() {
    Collections.shuffle(randomNumbers);
    return new ArrayList<>(randomNumbers.subList(0, 6));
}

subList로 반환한 List를 그대로 반환하는 것이 아니라 new를 통해 새로운 List로 반환해주었다.

 

 

아예 동시에 접근할 일이 없도록 수정함으로써 해결했다.

 

다른 환경에서도 해결 방법은 비슷하다.

만약 멀티 스레드 환경에서 위 문제가 발생했다면 한번에 한 스레드만 접근 가능하도록 설정할 수도 있다.


참고

  1. https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html
반응형