ConcurrentModificationException

TIL·2023년 1월 9일

자료구조

목록 보기
5/5

  • 순회하면서 특정 요소를 검색하여 삭제하려는 경우 컬렉션 프레임워크 에서만 볼수 있는
  • for 순회 중 특정 요소가 삭제되어 인덱싱이 변경 되므로 발생 (like StringTokenizer.next())
  • 객체 원본 자체를 건드리면 안됨

public class Main {
    public static void main(String[] args) {
        List<String> stringList = init();

        error(stringList);
//        solution1(stringList);
//        solution2(stringList);
//        solution3(stringList);
//        solution4(stringList);
//        solution5(stringList);
//        solution6(stringList);
//        solution7(stringList);
    }

    // ArrayList 초기화
    public static List<String> init() {
        List<String> stringList = new ArrayList<>();
        stringList.add("a");
        stringList.add("b");
        stringList.add("c");
        stringList.add("d");
        stringList.add("a");
        return stringList;
    }

    // 0. for + remove() -> concurrentModifyException
    // for 순회 중 특정 요소가 삭제되어 인덱싱이 변경 되므로 발생 (like StringTokenizer.next())
    // "a", "b", "c", "d", "a"
    //  0    1    2    3    4 - index
    //       0    1    2    3 - index
    public static void error(List<String> stringList) {
        System.out.println("stringList = " + stringList);
        for( String str : stringList ) { // size 5 -> 4
            if ( str.equals("a") ) {
                stringList.remove(str);
                // ArrayList.remove() 에서 modCount++ (modificationCount++) (삭제 횟수 카운트)
            }
        }
        System.out.println("stringList = " + stringList);
    }

    // 1. 역순으로 순회하며 삭제
    // 뒤에서 부터 삭제하면 앞의 애들은 인덱싱 안바뀜
    // "a", "b", "c", "d", "a"
    //  0    1    2    3    4 - index
    //  0    1    2    3      - index
    public static void solution1(List<String> stringList) {
        System.out.println("stringList = " + stringList);
        for( int i = stringList.size() - 1; i >= 0; i-- ) { // 역순
            String str = stringList.get(i);
            if ( str.equals("a") ) {
                stringList.remove(str);
            }
        }

        System.out.println("stringList = " + stringList);
    }

    // 2. 삭제할 요소를 찾아서 다른 곳에 저장한 후 removeAll()로 삭제
    // removeAll() : ConcurrentException Handling (한번의 순회마다 바뀐 인덱싱을 새로운 곳에 저장)
    public static void solution2(List<String> stringList) {
        List<String> removed = new ArrayList<>();
        for ( String str : stringList ) {
            if ( str.equals("a") ) {
                removed.add(str);
            }
        }
        stringList.removeAll(removed);
        System.out.println("stringList = " + stringList);
    }

    // 3. removeIf()로 요소 삭제
    // 내부 구현은 solution6와 같음 (JDK8+)
    // 삭제할 요소의 정의가 구현된 Lambda를 인자로 전달
    public static void solution3(List<String> stringList) {
        stringList.removeIf(str -> str.equals("a"));
        System.out.println("stringList = " + stringList);
    }

    // 4. try-catch
    // 중복된 원소 삭제 불가
    // 첫번째 a에서 catch 이후 for문 종료 되므로 두번째 a 삭제 안되고 다시 ConcurrentModificationException
    public static void solution4(List<String> stringList) {
        System.out.println("stringList = " + stringList);
        try {
            for( String str : stringList ) {
                if ( str.equals("a") ) {
                    stringList.remove(str);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("stringList = " + stringList);
    }

    // 5. remove 하고 break 로 빠져나가기
    // 중복된 원소 삭제 불가
    public static void solution5(List<String> stringList) {
        System.out.println("stringList = " + stringList);
        for( String str : stringList ) {
            if ( str.equals("a") ) {
                stringList.remove(str);
                break; // 해당 if문만 종료 != return
            }
        }
        System.out.println("stringList = " + stringList);
    }

    // 6. List 객체 자체가 아닌 iterator를 제거
    // Set, Map => index 개념이 없으므로 인덱싱 직접 하는 대신 iterator 사용
    public static void solution6(List<String> stringList) {
        System.out.println("stringList = " + stringList);
        Iterator<String> iterator = stringList.iterator();
        while(iterator.hasNext()) {
            String str = iterator.next();
            if ( str.equals("a") ) {
                iterator.remove(); // 현재 요소를 기본 iterator 에서 제거 (next() 후에 호출)
            }
        }
        System.out.println("stringList = " + stringList);
    }

    // 7. CopyOnWriteArrayList 에 ArrayList 요소 복사 -> 복사본 수정, 원본에 갱신
    // 멀티 스레드 환경에서 안전 (synchronized)
    // iterator, 역순, try ~ catch 삭제 => 멀티 스레드 환경에서는 삭제 잘 안될 수도 있다
    // 읽기 Performance 에서 우월
    public static void solution7(List<String> stringList) {
        List<String> copy = new CopyOnWriteArrayList<>(stringList);
        System.out.println("copy = " + copy);
        for( String str : copy ) {
            if ( str.equals("a") ) {
                copy.remove(str);
            }
        }
        System.out.println("copy = " + copy);
    }
}

0개의 댓글