본문 바로가기
학습

Fail-Fast vs Fail-Safe

코동이 2020. 7. 23.

개요


자바에서는 Fail-Fast라는 용어가 있습니다. 말 그대로 빠르게 실패한다는 의미로, 자바 컬렉션을 사용할 때 언제 어떻게 사용한는지 알아보겠습니다.

 

Fail-Fast vs Fail-Safe Iterator


 자바 컬렉션은 fail-safe와 fail-fast 2가지 Iterator 타입이 있습니다. fail-safe는 컬렉션이 순환 도중에 변경이 가능한 경우이고, fail-fast는 컬렉션이 순환 도중에 변경이 불가능한 경우입니다. 즉, 비동기적인 작업 중에 동시적인 변경을 보장하지 못합니다. fail-fast를 해결하기 위해서는, 복사본을 사용하거나, 동기화를 보장하거나, Iterator 대신에 Enumeration을 사용하면 됩니다.

 

 예를 들어, ArrayList의 경우, 순환을 할 때, 다른 쓰레드가 해당 컬렉션을 변경해서는 안됩니다. 만약에 Iterator가 이미 순환을 하고 있는데 새로운 요소의 추가나 삭제 시도를 감지한다면, ArrayList는 fail-fast이므로 ConccurrentModificationException을 던집니다. Iterator.remove()에서는 예외를 던지 않고 컬렉션의 remove()에서만 예외를 던집니다.

 

  동시에 발생하는 수정(Concurrent Modification) : 다른 작업이 이미 동작하고 있는 중에 동시적으로 객체가 변경되는 것을 의미합니다. 자바의 경우, 다른 쓰레드가 iterator를 수행하는 도중에 collection을 변경하는 경우입니다.  이 때 ConcurrentModificationException을 던집니다.

 

// Java code to demonstrate remove 
// case in Fail-fast iterators 
  
import java.util.ArrayList; 
import java.util.Iterator; 
  
public class FailFastExample { 
    public static void main(String[] args) 
    { 
        ArrayList<Integer> al = new ArrayList<>(); 
        al.add(1); 
        al.add(2); 
        al.add(3); 
        al.add(4); 
        al.add(5); 
  
        Iterator<Integer> itr = al.iterator(); 
        while (itr.hasNext()) { 
            if (itr.next() == 2) { 
                // will not throw Exception 
                itr.remove(); 
            } 
        } 
  
        System.out.println(al); 
  
        itr = al.iterator(); 
        while (itr.hasNext()) { 
            if (itr.next() == 3) { 
                // will throw Exception on 
                // next call of next() method 
                al.remove(3); 
            } 
        } 
    } 
} 

 

 단,  itr.remove()는 복사본을 제거하기 때문에 오류를 발생시키지 않고  al.remove()는 복사본이 아닌 collection을 제거하기 때문에 예외를 던집니다.

 

 

  • 왜 컬렉션은 Fail-Fast 전략을 사용할까요?

Fail-Fast는 문제를 감지하는 즉시 예외를 던집니다. 왜냐하면 미래에 예측 불가능한 의도하지 않은 문제가 발생하는 것을 원천적으로 차단하기 위해서입니다.

 

 

 

Fast-Fail 예방하기


Fast-Fail을 예방하기 위해 복사본을 만드는 방법과 동기화를 보장하는 방법을 알아보겠습니다.

 

 

1. 복사본 만들기


 CopyOnWriteArrayList로 배열을 복사하여 사용하면, 순환 도중에 추가 혹은 삭제를 하여로 오류를 던지지 않습니다.

 

// Java code to illustrate 
// Fail Safe Iterator in Java 
import java.util.concurrent.CopyOnWriteArrayList; 
import java.util.Iterator; 
  
class FailSafe { 
    public static void main(String args[]) 
    { 
        CopyOnWriteArrayList<Integer> list 
            = new CopyOnWriteArrayList<Integer>(new Integer[] { 1, 3, 5, 8 }); 
        Iterator itr = list.iterator(); 
        while (itr.hasNext()) { 
            Integer no = (Integer)itr.next(); 
            System.out.println(no); 
            if (no == 8) 
  
                // This will not print, 
                // hence it has created separate copy 
                list.add(14); 
        } 
    } 
} 

 

 

 

2. 동기화를 보장하는 컬렉션 사용


 동기화를 보장하는 컬렉션을 사용하면 해결 할 수 있습니다. 예를들어, ConcurentHashMap 컬렉션을 사용하면  fail-safe하기 때문에  멀티쓰레드 환경에서 안정으로 동작하며 오류를 던지지 않습니다. 주기적으로 동기화되고 특정한 시점의 상태만 반영하기 때문에 최근에 변경된 내용은 저장되지 않을 수도 있습니다.

 

// Java program to illustrate 
// Fail-Safe Iterator which 
// does not create separate copy 
import java.util.concurrent.ConcurrentHashMap; 
import java.util.Iterator; 
  
public class FailSafeItr { 
    public static void main(String[] args) 
    { 
  
        // Creating a ConcurrentHashMap 
        ConcurrentHashMap<String, Integer> map 
            = new ConcurrentHashMap<String, Integer>(); 
  
        map.put("ONE", 1); 
        map.put("TWO", 2); 
        map.put("THREE", 3); 
        map.put("FOUR", 4); 
  
        // Getting an Iterator from map 
        Iterator it = map.keySet().iterator(); 
  
        while (it.hasNext()) { 
            String key = (String)it.next(); 
            System.out.println(key + " : " + map.get(key)); 
  
            // This will reflect in iterator. 
            // Hence, it has not created separate copy 
            map.put("SEVEN", 7); 
        } 
    } 
} 

 

 

  • 컬렉션의 remove()와 순환자(Iterator) remove()의 차이점은 무엇인가요?
  • 순환(iteration) 중에 요소를 삭제하려면 어떤 것을 사용해야 하나요?

 

컬렉션 인터페이스 remove(Object obj) 는 컬렉션에 있는 객체를 제거합니다. 리스트 인터페이스 remove(int index)는 특별 인덱스에 있는 객체를 제거합니다. 위 2개의 메서드는 순환 중(iterating)이 아닌 컬렉션에 있는 객체를 제거합니다.

순환에서는 다릅니다. 만약 모든 요소를 순회하다가 하나의 요소를 제거한다고 가정해 보겠습니다, 이 때 Iterator의 remove()를 사용합니다. 이 때는 순환자(Iterator)의 관점에서 요소를 제거합니다.

순환(iteration) 도중 컬렉션이나 리스트의 remove() 를 사용하면, CoccurentModificationException을 던집니다. 따라서 컬렉션 요소를 제거할 때는 순환자(Iterator) remove() 를 사용해야 합니다. 

 

 

 

참고

https://stackoverflow.com/questions/33351946/why-iterators-are-called-fail-safe-or-fail-fast

https://www.geeksforgeeks.org/fail-fast-fail-safe-iterators-java/

반응형

'학습' 카테고리의 다른 글

Hash 충돌 회피 알고리즘  (0) 2020.07.24
Iterator & Enumeration & ListIterator  (0) 2020.07.23
ArrayList & Vector 차이점  (0) 2020.07.23
Compile vs Interpretation  (0) 2020.07.23
다형성 / Up-casting & Down-casting  (0) 2020.07.20