首页 > 代码库 > fail-fast vs fail-safe iterator in Java

fail-fast vs fail-safe iterator in Java

Reference

[1] http://netjs.blogspot.co.uk/2015/05/fail-fast-vs-fail-safe-iterator-in-java.html

The collections which are there from Java 1.2 (or even legacy) like ArrayList, Vector, HashSet, HashMap all have fail-fast iterator whereas Concurrent collections added in Java 1.5 like ConcurrrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet have fail-safe iterator.

The key differences between the fail-fast and fail-safe iterator

    1. fail-fast iterator throws ConcurrentModificationException if the underlying collection is structurally modified in any way except through the iterator‘s own remove or add methods.
      fail-safe iterator doesn‘t throw ConcurrentModificationException.
    2. fail-fast iterator work on the original collection.
      fail-safe iterator makes a copy of the underlying structure and iteration is done over that snapshot. Drawback of using a copy of the collection rather than original collection is that the iterator will not reflect additions, removals, or changes to the collection since the iterator was created.
    3. fail-fast iterator provides remove, set, and add operations. Note that not all the iterators support all these methods. As exp.ListIterator supports add() method but the general iterator doesn‘t.
      With fail-safe iterator element-changing operations on iterators themselves (remove, set, and add) are not supported. These methods throw UnsupportedOperationException.

fail-fast iterator in Java

An iterator is considered fail-fast if it throws a ConcurrentModificationException under either of the following two conditions:

  • In multi-threaded environment, if one thread is trying to modify a Collection while another thread is iterating over it.
  • Even with single thread, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

fail-fast iterator fails if the underlying collection is structurally modified at any time after the iterator is created, thus the iterator will throw a ConcurrentModificationException if the underlying collection is structurally modified in any way except through the iterator‘s own remove or add (if applicable as in list-iterator) methods.

Note that structural modification is any operation that adds or deletes one or more elements; merely setting the value of an element (in case of list) or changing the value associated with an existing key (in case of map) is not a structural modification.

Mostly Iterators from java.util package throw ConcurrentModificationException if collection was modified by collection‘s methods (add / remove) while iterating

Also note that according to Oracle Docs fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

1) The following codes will throw ConcurrentModificationException

public class FailFastModification {
    public static void main(String[] args) {
        // creating map
        Map <String,String> cityMap = new HashMap<String,String>();
        cityMap.put("1","New York City" );
        cityMap.put("2", "New Delhi");
        cityMap.put("3", "Newark");
        cityMap.put("4", "Newport");
        // getting iterator
        Iterator <String> itr = cityMap.keySet().iterator();
        while (itr.hasNext()){
            System.out.println(cityMap.get(itr.next()));
            // trying to add new value to a map while iterating it, will throw ConcurrentModificationException
            cityMap.put("5", "New City");
        }        
    }

}

2) The following method to just update values is allowed and will not throw ConcurrentModificationException

public class FailFastModification {
    public static void main(String[] args) {
        // creating map
        Map <String,String> cityMap = new HashMap<String,String>();
        cityMap.put("1","New York City" );
        cityMap.put("2", "New Delhi");
        cityMap.put("3", "Newark");
        cityMap.put("4", "Newport");
        // getting iterator
        Iterator <String> itr = cityMap.keySet().iterator();
        while (itr.hasNext()){
            System.out.println(cityMap.get(itr.next()));
            // updating existing value while iterating
            cityMap.put("3", "New City");
        }        
    }
}

2) The following method to remove values using iterator itself is allowed and will not throw ConcurrentModificationException

public class FailFastModification {
    public static void main(String[] args) {
        Map <String,String> cityMap = new HashMap<String,String>();
        cityMap.put("1","New York City" );
        cityMap.put("2", "New Delhi");
        cityMap.put("3", "Newark");
        cityMap.put("4", "Newport");
        System.out.println("size before iteration " + cityMap.size());
        Iterator <String> itr = cityMap.keySet().iterator();
        while (itr.hasNext()){
            System.out.println(cityMap.get(itr.next()));
            // removing value using iterator remove method
            itr.remove();
        }
        System.out.println("size after iteration " + cityMap.size());        
    }
}

Fail Safe iterator

In case of fail-safe iterator, ConcurrentModificationException is not thrown as the fail-safe iterator makes a copy of the underlying structure and iteration is done over that snapshot. 
Since iteration is done over a copy of the collection so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException.

Drawback of using a copy of the collection rather than original collection is that the iterator will not reflect additions, removals, or changes to the collection since the iterator was created. Element-changing operations on iterators themselves (remove, set, and add) are not supported. These methods throw UnsupportedOperationException.

Iterator of CopyOnWriteArrayList is an example of fail-safe Iterator also iterator provided by ConcurrentHashMap keySet is fail-safe and never throws ConcurrentModificationException.

Look at the following example of a fail-safe iterator

public class FailSafeTest {
 public static void main(String[] args) {

  List <String> cityList = new CopyOnWriteArrayList<String>();
  cityList.add("New York City");
  cityList.add("New Delhi");
  cityList.add("Newark");
  cityList.add("Newport");  
  Iterator<String> itr = cityList.iterator();
  while (itr.hasNext())
         {         
          System.out.println(itr.next());
          cityList.add("NewCity");
          //itr.remove();
         }
 }
}

This program won‘t throw ConcurrentModificationException as iterator used with CopyOnWriteArrayList is fail-safe iterator.

If we uncomment the line //itr.remove(); this program will throw UnsupportedOperationException as fail-safe iterator does not support remove, set, and add operations.

Summary

  • An iterator is considered fail-fast if it throws a ConcurrentModificationException in case the underlying collection‘s structure is modified.
  • While iterating a list or a map values can be updated, only if an attempt is made to add or remove from the collection ConcurrentModificationException will be thrown by fail-fast iterator.
  • Fail-fast iterators throw ConcurrentModificationException on a best-effort basis and fail-fast behavior of an iterator cannot be guaranteed.
  • fail-safe iterator works with a copy of the collection rather than the original collection thus interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException.
  • remove, set, and add operations are not supported with fail-safe iterator.

fail-fast vs fail-safe iterator in Java