June 21, 2022

Fail-fast And Fail-safe Iterators in Java

In Java Collections framework you would have seen that the Collections like ArrayList, HashMap return an iterator that is termed as fail-fast iterator where as the concurrent collections like CopyOnWriteArrayList, ConcurrentHashMap return an iterator that is termed as fail-safe iterator. In this post we’ll see what are fail-fast and fail-safe iterators in Java and difference between fail-fast and fail-safe iterators in Java.

fail-fast iterator in Java

A fail-fast iterator in Java is an iterator which throws a ConcurrentModificationException if the underlying collection is structurally modified at any time after the iterator is created except through the iterator's own remove or add (applicable in case of ListIterator) 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 termed as structural modification.

fail-fast iterator internal working in Java

In the collections like ArrayList, HashSet where iterator is fail-fast the implementation classes have an int variable modCount that stores the count of the number of times the collection has been structurally modified.

Whenever a next value is fetched while iteration of the collection a check is made for the equality of modCount and expectedModCount if both are not equal the iterator fails throwing a ConcurrentModificationException.

fail-fast iterator Java example

Let’s see an example where a value is added to the HashMap while it is iterated.

public class FailFastDemo {
  public static void main(String[] args) {
    Map<String, String> carMap = new HashMap<String, String>();
    carMap.put("1", "Audi");
    carMap.put("2", "BMW");
    carMap.put("3", "Jaguar");
    carMap.put("4", "Mini Cooper");
    // iterating map
    Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator();
    while(itr.hasNext()) {
      Map.Entry<String, String> entry = itr.next();
      System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue());
      // adding value to Map
      carMap.put("5", "Mercedes");
    }
  }
}
Output
Exception in thread "main" Key is 1 Value is Audi java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1498)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1531)
	at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1529)
	at com.knpcode.FailFastDemo.main(FailFastDemo.java:19)

As you can see ConcurrentModificationException is thrown because of the attempt to structurally modify the Map while it is iterated using an iterator.

As already stated merely modifying the value in a collection that returns fail-fast iterator won’t result in ConcurrentModificationException being thrown.

public class FailFastDemo {
  public static void main(String[] args) {
    Map<String, String> carMap = new HashMap<String, String>();
    carMap.put("1", "Audi");
    carMap.put("2", "BMW");
    carMap.put("3", "Jaguar");
    carMap.put("4", "Mini Cooper");
    // iterating map
    Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator();
    while(itr.hasNext()) {
      Map.Entry<String, String> entry = itr.next();
      if(entry.getKey().equals("3")) {
        // Modifying value for existing key
        carMap.put("3", "Mercedes");
      }
      System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue());        
    }
  }
}
Output
Key is 1 Value is Audi
Key is 2 Value is BMW
Key is 3 Value is Mercedes
Key is 4 Value is Mini Cooper

As you can see here value is modified for an existing key which is OK and doesn't throw ConcurrentModificationException.

Using iterator’s remove method

You can use iterator’s remove method to remove an element while iterating without causing ConcurrentModificationException.

public class FailFastDemo {
  public static void main(String[] args) {
    Map<String, String> carMap = new HashMap<String, String>();
    carMap.put("1", "Audi");
    carMap.put("2", "BMW");
    carMap.put("3", "Jaguar");
    carMap.put("4", "Mini Cooper");
    // iterating map
    Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator();
    while(itr.hasNext()) {
      Map.Entry<String, String> entry = itr.next();
      if(entry.getKey().equals("3")) {
        // removing using iterator's remove method
        itr.remove();
      }
    }
    System.out.println("** After element removal **");
    for(String key : carMap.keySet()){
      System.out.println("Key is " + key + " Value is " + carMap.get(key));
    }
  }
}
Output
** After element removal **
Key is 1 Value is Audi
Key is 2 Value is BMW
Key is 4 Value is Mini Cooper

fail-safe iterator in Java

A fail-safe iterator in Java is an iterator that works on the snap shot of the collection at some point of time. So any structural modification in the underlying collection is not guaranteed to be reflected while iterating.

The implementation differs in different concurrent collections. For example in case of CopyOnWriteArrayList, iterator method uses a reference to the state of the array at the point that the iterator was created. The iterator will not reflect additions, removals, or changes to the list since the iterator was created.

In case of ConcurrentHashMap iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. So in case of ConcurrentHashMap it may show the modified value.

fail-safe iterator ConcurrentHashMap example

public class FailSafeDemo {
  public static void main(String[] args) {
    Map<String, String> carMap = new ConcurrentHashMap<String, String>();
    carMap.put("1", "Audi");
    carMap.put("2", "BMW");
    carMap.put("3", "Jaguar");
    carMap.put("4", "Mini Cooper");
    // iterating map
    Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator();
    while(itr.hasNext()) {
      Map.Entry<String, String> entry = itr.next();
      System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue());
      carMap.putIfAbsent("5", "Mercedes");
    }
    System.out.println("Size- " + carMap.size());
  }
}
Output
Key is 1 Value is Audi
Key is 2 Value is BMW
Key is 3 Value is Jaguar
Key is 4 Value is Mini Cooper
Key is 5 Value is Mercedes
Size- 5

The element which was added while iterating the Map is also displayed.

fail-safe iterator CopyOnWriteArrayList example

public class FailSafeDemo {
  public static void main(String[] args) {
    List<String> carList = new CopyOnWriteArrayList<>();
    carList.add("Audi");
    carList.add("BMW");
    carList.add("Jaguar");
    carList.add("Mini Cooper");  
    boolean addFlag = false;
    Iterator<String> itr = carList.iterator();
    while(itr.hasNext()) {
      System.out.println(itr.next());
      // add element to the list
      if(!addFlag){
       carList.add("Mercedes");
       addFlag = true;
      }
    }
    System.out.println("-- List after addition -- ");
    itr = carList.iterator();
    while(itr.hasNext()) {
       System.out.println(itr.next());
    }
  }
}
Output
Audi
BMW
Jaguar
Mini Cooper
-- List after addition -- 
Audi
BMW
Jaguar
Mini Cooper
Mercedes

Here you can see that the element is added to the CopyOnWriteArrayList while iteration but it is not reflected in that iteration as in case of CopyOnWriteArrayList iterator uses a reference to the underlying array at the point that the iterator was created.

Fail-fast Vs Fail-safe iterator in Java

  1. Fail-fast iterator throws ConcurrentModificationException if the underlying collection is modified at any time after the iterator is created.
    Fail-safe iterator doesn’t throw ConcurrentModificationException if the underlying collection is modified at any time after the iterator is created.
  2. In case of fail-fast iterator, since the underlying collection can’t be changed so no need to clone the collection or take a snap shot of collection at any point of time.
    In case of fail-safe iterator, either copy of underlying collection is used or a snap shot of collection at any point of time is used making the fail-safe iterator weakly consistent.
  3. Fail-fast iterators have their own remove and add methods that can be used to modify the collection while iterating.
    In case of fail-safe iterator based on the implementation element-changing operations on iterators themselves (remove, set, and add) may not be supported as in the case of CopyOnWriteArrayList. These methods throw UnsupportedOperationException.

That's all for the topic Fail-fast And Fail-safe Iterators in Java. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment