티스토리 뷰
자바 컬렉션의 iterator 는 fail-safe와 fail-fast를 지원한다.
각 정의는 무엇이고 iterator에서 어떻게 동작하는지 알아보려고한다.
fail-safe 와 fail-fast
fail-fast는 가능한 빨리 작업을 중단하여 오류를 즉시 노출하고 작업을 중지하는 기능이다.
fail-safe는 장애가 발생하더라도 작업을 중단하지 않으며 가능한 오류 발생을 피하려고 한다.
컬렉션 iterator에서의 fail-safe 와 fail-fast
iterator 는 컬렉션(List, Set, Map 등)의 요소를 순차적으로 접근하고 탐색하는 방법을 제공하는 객체이다.
컬렉션의 요소를 탐색할 때, 컬렉션의 수정이 발생하면 컬렉션의 iterator는 처리하는 방식이 다르다.
fail-safe 방식에서는 컬렉션 내 요소를 탐색할 때 컬렉션이 수정되더라도 예외를 발생시키지 않고 순회를 계속 진행한다. 내부적으로 컬렉션의 복사본을 생성하여 이를 순회하므로 원본 컬렉션이 수정이 되더라도 예외를 발생시키지 않는다. 이 방식을 지원하는 자바 컬렉션으로는 ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet이 있다.
fail-fast 방식에서는 컬렉션 내 요소를 탐색할 때 iterator 자체적으로 지원하는 추가, 삭제 메서드를 제외하고 컬렉션이 수정되면 즉시 ConcurrentModificationException 을 발생시켜 순회를 중단한다. 동시 수정을 빠르게 감지하고 예외를 던짐으로써 예기치 않은 오류를 방지하는데 도움을 준다.
최선을 다해 ConcurrentModificationException을 발생시키지만, 동기화되지 않은 상태에서 동시 수정이 있는 경우에 즉시 예외를 발생시킨다는 보장은 불가능하다. 따라서, 이 예외에 의존하여 코드를 작성하지 말아야 한다. 이 방식을 지원하는 자바 컬렉션으로는 ArrayList, LinkedList, Vector, HashMap, HashSet이 있다.
fail-safe 동작 원리 및 fail-safe 처리 예제 분석
동작 원리
1. snapshot 필드 사용 및 컬렉션의 복사본 생성
iterator는 컬렉션과 동일한 타입의 배열인 필드 snapshot을 선언해놓고, 해당 필드에 복사본을 생성한다.
2. 순회 시작
컬렉션의 복사본인 snapshot을 기준으로 순회를 진행한다. 원본 컬렉션이 수정되더라도 snapshot의 데이터는 영향을 받지 않는다.
3. 순회 계속 진행
원본 컬렉션이 수정되는 동안에도 복사본인 snapshot을 기준으로 순회를 진행하며 예외를 발생시키지 않고 데이터를 안정적으로 읽을 수 있다.
4. 순회 완료
snapshot의 모든 요소를 다 읽으면 순회가 완료된다.
CopyOnWriteArrayList의 fail-safe 처리
fail-safe를 지원하는 컬렉션 중 CopyOnWriteArrayList 클래스의 코드 일부를 가져왔다.
해당 코드는 iterator 메서드를 호출하였을 때, 어떻게 fail-safe 동작을 진행하는지 보여준다.
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private transient volatile Object[] array; (1)
public ListIterator<E> iterator() {
return new COWIterator<E>(getArray(), 0); (2)
}
static final class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot; //array의 스냅샷
private int cursor;
COWIterator(Object[] es, int initialCursor) {
cursor = initialCursor;
snapshot = es; (3)
}
public boolean hasNext() { (4)
return cursor < snapshot.length;
}
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++]; (5)
}
}
}
(1) array 는 CopyOnWriteArrayList 내부적으로 사용하는 동적 배열이다. (CopyOnWriteArrayList는 배열 기반의 리스트이다.)
(2) iterator 메서드가 호출되면 ListIterator 인터페이스를 구현한 COWIterator 객체를 반환한다.
(3) array 값을 COWIterator 클래스의 지역변수인 snapshot 에 저장함으로써, 객체가 복사된다.
(4) 복제본인 snapshot 을 기준으로 요소가 있는지 확인한다.
(5) next 메서드를 통해 컬렉션내의 요소를 가져올 때 snapshot(복사본)에서 가져온다. 이는, 원본 컬렉션이 수정되더라도 영향을 받지 않는다.
fail-fast 동작 원리 및 fail-fast 처리 예제 분석
동작 원리
1. modCount 필드 사용
컬렉션이 구조적으로 수정된 횟수를 추적하기 위한 필드이다. 컬렉션이 구조적으로 수정된 횟수를 기록하며, 컬렉션에 데이터 추가, 삭제 등의 메서드를 통해 값이 변경된다. iterator 객체는 modCount 값을 expectedModCount 라는 지역 변수에 저장한다.
2. 순회 시작 및 동시 수정 감지
iterator는 순회를 진행하면서, modCount 와 expectedModCount 값을 비교한다. 값이 일치하면 계속해서 순회를 진행하고, 일치하지 않으면 컬렉션이 구조적으로 수정되었음을 판단한다.
5. 예외 발생
컬렉션이 구조적으로 수정되었음을 판단함으로써 iterator는 ConcurrentModificationException을 발생시킨다. 이는 iterator가 더 이상 정상적으로 작동하지 않음을 알려줌으로써, 수정된 데이터를 기반으로 잘못된 동작 진행을 방지한다.
ArrayList의 fail-fast 처리
fail-fast를 지원하는 컬렉션 중 ArrayList 클래스의 코드 일부를 가져왔다.
해당 코드는 iterator 메서드를 호출하였을 때, 어떻게 fail-fast 동작을 진행하는지 보여준다.
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
protected transient int modCount = 0; (1)
}
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public Iterator<E> iterator() { (2)
return new Itr();
}
..
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; (3)
@SuppressWarnings("unchecked")
public E next() {
checkForComodification(); (4)
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() { (5)
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
(1) modCount는,추상 클래스인 AbstractList 에 선언되어있다. 이 필드는 protected 로 선언되어 있어 이를 상속받는 클래스에서 접근할 수 있다.
(2) iterator 메서드가 호출되면 Iterator 인터페이스를 구현한 Itr 객체를 반환한다.
(3) modCount 값을 Itr 클래스의 지역변수인 expectedModCount 에 저장한다.
(4) next 메서드를 통해 컬렉션내의 요소를 가져올 때 먼저 checkForComodification 메서드를 호출한다.
(5) 컬렉션의 modCount와 expectedModCount 의 수가 다르면 컬렉션이 수정되었다고 판단하여 ConcurrentModificationException을 발생시킨다.
정리 및 비교
fail-safe
지원 컬렉션 ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet
snapshot 필드를 사용하여 컬렉션을 복사한다.
복사본을 생성하기 때문에 fail-fast에 비해 속도가 느리고 더 많은 메모리를 필요로하며, 업데이트된 컬렉션을 사용하지 않지만 동시 작업이 가능하다.
fail-fast
지원 컬렉션 ArrayList, LinkedList, Vector, HashMap, HashSetmodCount 필드를 사용하여 컬렉션의 수정 사항을 확인하여, 동시 수정이 진행되었을 경우에 예외를 발생시킨다.
참고
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ArrayList.html
https://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator
'Language > Java' 카테고리의 다른 글
메모리 관리: Generational Garbage Collection (1) | 2024.11.13 |
---|---|
HashMap : 해시 충돌이 발생하였을 때 (0) | 2024.11.11 |
Hash 기반 자료구조 (1) | 2024.11.09 |
Hash에 대해서 (0) | 2024.11.08 |
List Collection (0) | 2024.11.07 |
- Total
- Today
- Yesterday
- 티스토리챌린지
- nosql
- 추상클래스
- fail-fast
- 인스턴스변수
- 고정 세션
- Red-Black Tree
- JPA
- Hash
- fail-safe
- java
- object
- HashMap
- AutoConfiguration
- spring boot
- @conditional
- Caching
- Load Balancer
- Security
- Sticky Session
- Spring
- 인터페이스
- HashSet
- syncronized
- 다중화
- 로드 밸런서
- 정적변수
- 오블완
- 자동구성
- nginx
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |