본문 바로가기

Backend

[Springboot] List 순회 도중 ConcurrentModificationException 발생

반응형

 부모 엔티티와 자식 엔티티 간 양방향 일대다 연관관계를 삭제하기 위해 for loop를 돌면서 자식 엔티티 리스트에서 하나씩 지우는 작업을 수행하던 도중 ConcurrentModificationException을 만났다.

 

일반적으로 컬렉션은 비동기 상태로 사용이 되기 때문에 여러 스레드가 동시에 동일한 컬렉션을 건드리면 예상과 다른 결과가 나올 수 있다. 해당 예외는 동시성 작업을 제어하기 위해 도입이 된 것이었다.

 

Iterator는 내부적으로 컬렉션의 수정 상태를 추적하는데, ArrayList는 modCount 변수를 사용해서 컬렉션의 수정 횟수를 저장한다. expectedModCount와 비교하면서 초기상태를  추적한다. Iterator의 next 메소드를 호출할 때마다 두 값이 일치하는지 확인하고 만일 일치하지 않다면 다른 스레드 혹은 다른 곳에서 컬렉션의 변경이 있었다고 판단하고 해당 예외를 터트린다.

 안전하게 제거하려면 Iterator의 remove 메소드를 활용해서 modCount를 직접 관리하는 방법이 있다. 해당 메소드 remove()를 호출하면 초기상태값을 현재 컬렉션의 modCount에 맞춰 조정하는 것이다. 

 

 

<기존의 코드>

public ResponseEntity<ApiResponseWrapper> deleteCategory(DeleteCategoryRequestDto dto){

    Category category = categoryRepository.findById(dto.getId()).orElseThrow(() -> new EntityNotFoundException("카테고리 아이디가 존재하지 않습니다."));
    for (Topic topic : category.getTopics()) {
        topic.removeCategory();
        topicRepository.save(topic); // 변경 사항 저장
    }
    categoryRepository.delete(category);
    return ResponseEntity.ok(ApiResponseWrapper.success());
}

 

< 변경 후의 코드>

public ResponseEntity<ApiResponseWrapper> deleteCategory(DeleteCategoryRequestDto dto){
    Category category = categoryRepository.findById(dto.getId())
        .orElseThrow(() -> new EntityNotFoundException("카테고리 아이디가 존재하지 않습니다."));

    // Iterator를 사용하여 안전하게 토픽 제거
    Iterator<Topic> iterator = category.getTopics().iterator();
    while (iterator.hasNext()) {
        Topic topic = iterator.next();
        topic.removeCategory();  // 내부 상태 조정
        iterator.remove();       // 리스트에서 안전하게 토픽 제거
        topicRepository.save(topic); // 변경사항 저장
    }

    categoryRepository.delete(category);
    return ResponseEntity.ok(ApiResponseWrapper.success());
}

 

멀티스레드 환경이 아닌 만큼 이렇게만 코드를 작성해도 에러가 발생하지 않았다.

다만 멀티스레드 환경이라면 java.uti.concurrent 패키지에 정의된 클래스를 사용하라고 명시되어 있다. 


https://codechacha.com/ko/java-concurrentmodificationexception/

 

Java - ConcurrentModificationException 원인 및 해결 방법

ConcurrentModificationException는 보통 리스트나 Map 등, Iterable 객체를 순회하면서 요소를 삭제하거나 변경을 할 때 발생합니다. removeIf, removeAll, Iterable 등의 메소드를 이용하여 리스트 순회 중 요소를

codechacha.com

https://jyyoun1022.tistory.com/12

 

[JAVA] java 개발자라면 한번 쯤은 겪을 ConcurrentModificationException

지난 포스트에서는 일반 for문과 for-each 문과 Iterator에 대하여 포스팅했었습니다. 이번 포스팅은 for-each문에서 발생하는 ConcurrentModificationException에 대해서 포스팅하겠습니다. ConcurrentModificationExcep

jyyoun1022.tistory.com

 

반응형