Java - Fail Fast mechanism in Java collection

Article catalog

What is fail fast

https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html

The fail fast mechanism is an error mechanism in the Java collection.

When traversing a collection object with an iterator, if the structure of the collection object is modified (added or deleted) during traversal, Concurrent Modification Exception will be thrown.

for instance:

In a multithreaded environment, thread 1 is traversing the collection. At this time, thread 2 modifies the collection (add, delete, modify), and it is easy to throw a Concurrent Modification Exception.

Of course, in the case of single thread, Concurrent Modification Exception will also be thrown when the collection is modified (added, deleted and modified) during traversal

The iterator iterator and listIterator methods returned by this class fail quickly: if the iterator creates a structural modification after any time in the list, the iterator will throw a Concurrent Modification Exception in any way except through the iterator's own remove or add methods.

Therefore, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary and uncertain behavior at an uncertain time in the future.

Source code interpretation

Itr

During traversal, the set modification will fail fast, traversing the set -------- -- > iterator

/**
     * An optimized version of AbstractList.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;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            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];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

See, check for confirmation

 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

In modcount= A ConcurrentModificationException is thrown when expectedmodcount,

In the next method, checkForComodification is called, so it is possible to throw concurrent modification exceptions when traversing the collection.

Next, let's study when modCount and expectedModCount are not equal.

  • After creating an iterator, the initial value of expectedModCount is modCount,
  • Modifying the set will change the modCount,
  • expectedModCount will only be modified to modCount in the remove method of the iterator

It's all

Content in, except modCount. modCount is a constant of ArrayList. The default value is 0

Why concurrent modification exceptions occur when modifying the structure of a collection - source code analysis

Let's say that when traversing a collection object with an iterator, if the structure of the collection object is modified (added or deleted) during traversal, a Concurrent Modification Exception will be thrown.

remove of modification method

modCount + +, the latter modCount will not be equal to expectedModCount, and then a concurrent modification exception will be thrown.

add of modification method

The operation on modCount + + in the ensureCapacityInternal method changes the value of modCount, so it is called

Will the set method trigger fast fail?

The answer is No.

Set does not modify modCount + +, so modifying an element of the set will not fail fast

Case sharing

[case 1]

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String tmp = iter.next();
    System.out.println(tmp);
    if (tmp.equals("1")) {
        list.remove("1");
    }
}
 1
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:25)

The list# remove method was called

[case 2]

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String tmp = iter.next();
    System.out.println(tmp);
    if (tmp.equals("3")) {
        list.remove("3");
    }
}
1
2
3

The list# remove method is called, but no concurrent modification exception is thrown????

remove the penultimate element, but no exception is thrown at this time. Analyze it again

cursor is the subscript of the next variable to be returned

lastRet is the subscript of the last returned variable

The hasNext method tells us that there is a next element in the set only when the subscript of the next variable is not equal to size.

However, when removing, the size -- is changed. After deleting the element "3", the size becomes 3, and the cursor is also 3. When you go to hasNext, you will find that the cursor and size are equal. Then you will exit the traversal, and "4" will not be traversed at all.

So I didn't throw an exception, because I quit after remove and haven't had time to go to the next method~

[case 3]

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String tmp = iter.next();
    System.out.println(tmp);
    if (tmp.equals("4")) {
        list.remove("4");
    }
}
1
2
3
4
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:25)

Next case

Then delete "4", that is, the last element. It is reasonable to delete the last element and quit? Can't go to the next method?

In fact, it's wrong. After deleting "4", you don't quit directly! After remove, the size becomes 3, but the cursor is 4. When you go to hasNext, you will find 4= 3. It will enter the loop again. As a result, it goes to the next method and throws an exception...

[case 4]

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
for (String i : list) {
    if ("1".equals(i)) {
        list.remove("1");
    }
}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:22)

Using an enhanced for loop to traverse and decompile a class is essentially the same as using an iterator.

[case 5]

List<String> list = Arrays.asList("1", "2", "3", "4");
for (String i : list) {
    if ("1".equals(i)) {
        list.remove("1");
    }
}
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.remove(AbstractList.java:161)
	at java.util.AbstractList$Itr.remove(AbstractList.java:374)
	at java.util.AbstractCollection.remove(AbstractCollection.java:293)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:21)

The collection generated by the Array.asList() method throws an unsupported operationexception. It is found that the ArrayList generated by asList is a static internal class, not java.util.ArrayList. There are no such methods.

Therefore, the ArrayList generated by asList cannot be added, deleted or modified

Java development specification 01 - Collection_ Arrays.asList pit

[case 6]

    List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            String tmp = iter.next();
            System.out.println(tmp);
            if (tmp.equals("1")) {
                iter.remove();
            }
        }
1
2
3
4

[case 7]

```java
// Java code to illustrate
// Fail Fast Iterator in Java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class FailFastExample {
	public static void main(String[] args)
	{
		Map<String, String> cityCode = new HashMap<String, String>();
		cityCode.put("Delhi", "India");
		cityCode.put("Moscow", "Russia");
		cityCode.put("New York", "USA");

		Iterator iterator = cityCode.keySet().iterator();

		while (iterator.hasNext()) {
			System.out.println(cityCode.get(iterator.next()));

			// adding an element to Map
			// exception will be thrown on next call
			// of next() method.
			cityCode.put("Istanbul", "Turkey");
		}
	}
}
India
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
    at FailFastExample.main(FailFastExample.java:18)

// 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);
			}
		}
	}
}
[1, 3, 4, 5]
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at FailFastExample.main(FailFastExample.java:28)

Alibaba Java development manual

How to avoid throwing exceptions in fail fast

  1. If you have to modify the collection during traversal, it is recommended to use the methods such as remove of the iterator rather than the methods such as remove of the collection
  2. In a concurrent environment, you also need to lock the Iterator object, or you can directly use Collections.synchronizedList
  3. CopyOnWriteArrayList (fail safe)

Posted by GuitarGod on Wed, 03 Nov 2021 02:07:41 -0700