Article catalog
- What is fail fast
- Source code interpretation
- Case sharing
- Alibaba Java development manual
- How to avoid throwing exceptions in fail fast
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
- 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
- In a concurrent environment, you also need to lock the Iterator object, or you can directly use Collections.synchronizedList
- CopyOnWriteArrayList (fail safe)