Fail-Fast Iterator와 Fail-Safe Iterator

김대협·2023년 2월 18일
0

Fail-Fast Iterator와 Fail-Safe Iterator


Iterator는 자바 컬렉션 프레임워크(Java Collection Frameworks)의 일부분이다.
요소(Elements)를 하나씩 순회하는데 사용되며, 두 가지 유형의 Iterator Fail-Fast IteratorFail-Safe Iterator를 제공한다.

Fail-Fast Iterator는 실패 즉시 작업을 중단하고 Exception을 Throw 한다.
Fail-Safe Iterator는 실패 시 작업이 중단되지 않고, 가능한 실패를 회피하려고 한다.

Iterator를 이해하기에 앞서 Concurrent Modification에 대한 이해가 필요하다.

ConcurrentModificationException


ConcurrentModificationException은 자바 컬렉션 프레임워크 작업 중 발생하는 예외의 한 종류이다.
Concurrent Modification은 특정한 대상 객체에 동시에 구조적 수정을 가할 때 발생하는 것이다.
쉽게 말해, Iterator를 순회하는 동안 구조적 수정(추가, 삭제, 변경)을 시도하면 예외가 발생한다.

예외의 이름으로 인해, 멀티 스레드 환경에서만 발생한다는 착각을 많이 하지만 싱글 스레드 기반 환경에서도 이 예외는 발생할 수 있다.

싱글 스레드 환경에서 발생
: Enhanced for-loop 또는 iterator를 직접 사용 중 구조적 수정(추가, 삭제, 변경) 시 발생

멀티 스레드 환경에서 발생
: A 스레드가 Iterator를 사용한 컬렉션 순회 중 B 스레드가 컬렉션에 요소를 추가하거나 삭제할 경우 발생

public class ConcurrentModificationTest {

    public static void doTest() {
        List<String> list = new ArrayList<>();
        list.add( "A" );
        list.add( "B" );
        list.add( "C" );
        list.add( "D" );

        // Iterator를 취득하여 사용 중 발생
        Iterator<String> iter = list.iterator();
        while ( iter.hasNext() ) {
            String s = iter.next();
            if ( s.equals( "B" ) ) {
                list.remove( s ); // 삭제 시 'Concurrent Modification Exception 발생!!'
            }
        }

        // Enhanced for-loop 사용 중 발생
        /*for ( String s : list ) {
            if ( s.equals( "B" ) ) {
                list.remove( s ); // 삭제 시 'Concurrent Modification Exception 발생!!'
            }
        }*/

        System.out.println( list );
    }

    public static void main( String[] args ) {
        doTest();
    }
}

싱글 스레드 환경에서 ConcurrentModificationException 발생 예제 코드

상위 예제 코드를 통해 향상된 for문(Enhanced for-loop)의 경우도 내부적으로 Iterator를 사용하기 때문에 동일한 에러가 발생함을 확인할 수 있다.
Fail-Fast Iterator가 발생시키는 예외 종류이다 보니 인덱스를 통한 순차 접근 통한 삭제 시는 문제가 발생하지 않는다.

// 인덱스를 통한 접근으로 정상 삭제 가능
for ( int i = 0; i < list.size(); i++ ) {
    String s =  list.get( i );
    if ( s.equals( "B" ) ) {
        list.remove( s );
    }
}

Fail-Fast Iterator


Fail-Fast Iterator는 오류 보고 즉시 종료되며, ConcurrentModificationExeception을 Throw 한다.
해당 Iterator는 HashMap, ArrayList, Vector, HashSet 등 에서 사용되는 Iterator 이다.

Iterator 순회 중에 구조적 수정을 허용하지 않고, 원복 컬렉션을 사용하여 메모리 사용률이 낮으며, 실행 속도가 빠른 장점을 갖고 있다.

mod count를 내부적으로 갖고 컬렉션의 구조적 수정이 되었는지 여부와 상관없이 컬렉션의 상태를 파악하고
mod count를 컬렉션 수정이 일어날 때마다 업데이트하여 다음 값을 확인한다.
Iterator 생성 후에 mod count의 변경이 얼어나면 예외가 발생한다.

Fail-Safe Iterator (non-Fail fast Iterator)


Fail-Safe Iterator는 오류가 보고되더라도 즉시 중단하지 않고 동작이 이어진다.
해당 Iterator는 ConcurrentHashMap, CopyOnWriteArraylist 등 에서 사용되는 Iterator 이다.
JAVA SE Documents 에서는 non-fail fast iterator라는 명칭을 사용한다.

Fail-Safe Iterator는 구조적 수정을 허용하는 대신 복제 컬렉션에서 동작을 수행하는 특징으로 인해 메모리 사용률이 높고, 실행 속도가 Fail-Fast 대비 느린 편에 속합니다.

public static void doTestForFailSafe() {
    Map<String, Integer> map = new ConcurrentHashMap<>();
    map.put( "A", 10 );
    map.put( "B", 20 );
    map.put( "C", 30 );
    map.put( "D", 40 );

    Iterator<String> iterator = map.keySet().iterator();
    while ( iterator.hasNext() ) {
        String key = iterator.next();
        if ( key.equals( "B" ) ) {
            map.remove( key );
        }
    }

    System.out.println( map );
}

구조적 수정 시 문제없음을 확인하는 예제 코드

Reference


Fail Fast and Fail Safe Iterator in Java - javatpoint
How to Avoid the Concurrent Modification Exception in Java - Rollbar


© 2023.2 Written by Boseong Kim.
profile
기록하는 개발자

0개의 댓글