fail-fast (fast failure) java in the mechanism
Brief introduction
fail-fast mechanism, i.e. rapid failure mechanism, an error detection mechanism is a set of java. When structural changes in the course of the iterative collection of the collection is that there are fail-fast it may occur that ran ConcurrentModificationException exception. fail-fast mechanism does not guarantee under certain modifications are not synchronized throws an exception, it's just best to throw the past, so this mechanism is generally used only to detect bug
fail-fast emergence scene
In our common java set to fail-fast mechanism that may arise, such as the common ArrayList, HashMap. Are likely to experience rapid failure in a multi-threaded and single-threaded environment .
1. fail-fast example of the single-threaded environment:
public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0 ; i < 10 ; i++ ) { list.add(i + ""); } Iterator<String> iterator = list.iterator(); int i = 0 ; while(iterator.hasNext()) { if (i == 3) { list.remove(3); } System.out.println(iterator.next()); i ++; } }
Print Console:
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 com.example.springboot_demo.fail_fast_safe.ArrayListFailFast.main(ArrayListFailFast.java:24)
This code defines a collection Arraylist, and iterators to traverse, during traversal, deliberately remove an element in a certain iteration step, this time occurs fail-fast
2. Under multithreaded environment
public class ArrayListFailFastThreadPool { public static List<String> list = new ArrayList<>(); private static class MyThread1 extends Thread { @Override public void run() { Iterator<String> iterator = list.iterator(); while(iterator.hasNext()) { String s = iterator.next(); System.out.println(this.getName() + ":" + s); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } super.run(); } } private static class MyThread2 extends Thread { int i = 0; @Override public void run() { while (i < 10) { System.out.println("thread2:" + i); if (i == 2) { list.remove(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i ++; } } } public static void main(String[] args) { for(int i = 0 ; i < 10;i++){ list.add(i+""); } MyThread1 thread1 = new MyThread1(); MyThread2 thread2 = new MyThread2(); thread1.setName("thread1"); thread2.setName("thread2"); thread1.start(); thread2.start(); } }
Print Console:
Exception in thread "thread1" java.util.ConcurrentModificationException thread2:3 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.example.springboot_demo.fail_fast_safe.ArrayListFailFastThreadPool$MyThread1.run(ArrayListFailFastThreadPool.java:20)
Start two threads, one for each iteration of the list with the other in an iterative process threads 1 to remove an element, the result is thrown java.util.ConcurrentModificationException
Fail-fast principle
fail-fast ConcurrentModificationException is how to throw an exception, but also under what circumstances will throw?
We know, for a collection of the list, map type, we all can be traversed by the iterator, but Iterator is really just an interface specific implementation or specific internal class collection class that implements the Iterator and implement relevant methods. Here we have an example to the ArrayList class. In the ArrayList, when calling list.iterator (), its source code is:
public Iterator<E> iterator() { return new 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; 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 newConcurrentModificationException (); } }
As it can be seen, which is the key to determine whether the abnormal ConcurrentModificationException thrown. In this code segment, when modCount! = ExpectedModCount, the exception will be thrown. Obviously expectedModCount in addition to a start given initial value modCount outside, and did not recur throughout the iterative process of change, so change can occur only modCount.
In front of the analysis of the ArrayList expansion mechanism, you can know be add in the ArrayList, remove, clear isochronous operations involving modification of the number of elements in the set, will change ModCount (modCount ++) so that when another thread (concurrent modification) or with a thread during the traversal, make method calls the relevant number of the sets is changed , will make modCount change, so checkForComodification ConcurrentModificationException method throws an exception.
Similarly, the principle of occurrence hashMap is the same.
Avoid fail-fast
method 1
In single-threaded during traversal, if you want to remove operation, you can call the iterator's remove method instead of collections remove method
public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0 ; i < 10 ; i++ ) { list.add(i + ""); } Iterator<String> iterator = list.iterator(); int i = 0 ; while(iterator.hasNext()) { if (i == 3) { iterator.remove(); //迭代器的remove()方法 } System.out.println(iterator.next()); i ++; } }
Method 2
Using java and contracting (the java.util.concurrent) in place of class ArrayList and hashMap.
For HashMap, you can use ConcurrentHashMap, ConcurrentHashMap using the lock mechanism is thread-safe. In terms of iterations, ConcurrentHashMap use a different iterative manner. In this iterative manner, when then change the set after the iterator is created is no longer thrown ConcurrentModificationException, replaced by new data when new change so as not to affect the original data, and then complete the iterator will replace the head pointer the new data, so iterator thread can use the original old data, but can also be done to change the write thread concurrency. That will not happen iteration fail-fast, but not guaranteed to get the latest data.