Java (Part 3 and 4) ----- fail-fast mechanism, java ----- fail-fast
In the JDK Collection, we often see something similar to this:
For example, ArrayList:
Note: The Fast failure behavior of the iterator cannot be guaranteed, because in general, it is impossible to make any hard guarantee for non-synchronous concurrent modifications. The quick failure iterator will do its best to throw ConcurrentModificationException. Therefore, writing a program dependent on this exception to improve the correctness of such iterators is a wrong practice: the fast failure behavior of the iterator should only be used to detect bugs.
In HashMap:
Note: The Fast failure behavior of the iterator cannot be guaranteed. In general, it is impossible to make any firm guarantee when there are non-synchronous concurrent modifications. The quick failure iterator tries its best to throw ConcurrentModificationException. Therefore, writing a program dependent on this exception is incorrect. The correct practice is that the fast failure behavior of the iterator should only be used to detect program errors.
These two paragraphs repeatedly refer to "quick failure ". What is the "quick failure" mechanism?
"Quick failure" is fail-fast, which is an error detection mechanism in Java sets. When multiple threads change the structure of the set, the fail-fast mechanism may occur. Remember that it is possible, not necessarily. For example, if there are two threads (thread 1 and thread 2), thread 1 traverses the elements in set A through Iterator, at A time, thread 2 modifies the structure of set A (modification above the structure, rather than simply modifying the content of the Set element). At this time, the program will throw A ConcurrentModificationException, this produces the fail-fast mechanism.
I. fail-fast example
Public class FailFastTest {private static List <Integer> list = new ArrayList <> ();/*** @ desc: one-thread iteration list * @ Project: test * @ file: failFastTest. java * @ Authro: chenssy * @ data: July 26, 2014 */private static class threadOne extends Thread {public void run () {Iterator <Integer> iterator = list. iterator (); while (iterator. hasNext () {int I = iterator. next (); System. out. println ("ThreadOne traversal:" + I); try {Thread. sleep (10);} catch (InterruptedException e) {e. printStackTrace () ;}}}/*** @ desc: When I = 3, modify list * @ Project: test * @ file: FailFastTest. java * @ Authro: chenssy * @ data: July 26, 2014 */private static class threadTwo extends Thread {public void run () {int I = 0; while (I <6) {System. out. println ("ThreadTwo run:" + I); if (I = 3) {list. remove (I) ;} I ++ ;}} public static void main (String [] args) {for (int I = 0; I <10; I ++) {list. add (I);} new threadOne (). start (); new threadTwo (). start ();}}
Running result:
ThreadOne traversal: 0 ThreadTwo run: 0 ThreadTwo run: 1 ThreadTwo run: 2 ThreadTwo run: 3 ThreadTwo run: 4 ThreadTwo run: 5 Exception in thread "Thread-0" java. util. concurrentModificationException at java. util. arrayList $ Itr. checkForComodification (Unknown Source) at java. util. arrayList $ Itr. next (Unknown Source) at test. arrayListTest $ threadOne. run (ArrayListTest. java: 23)
Ii. Causes of fail-fast
Through the above examples and explanations, I initially know that the cause of fail-fast is that when the program iterates the collection, a thread modifies the collection structure, in this case, the iterator will throw a ConcurrentModificationException and generate fail-fast.
To understand the fail-fast mechanism, we must first understand the ConcurrentModificationException. This exception is thrown when the method detects concurrent object modifications but does not allow such modifications. At the same time, it should be noted that this exception does not always indicate that the object has been modified concurrently by different threads. If a single thread violates the rules, it may also throw a change exception.
It is true that the fast failure behavior of the iterator cannot be guaranteed, but it cannot be guaranteed that this error will occur. However, the quick failure operation will do its best to throw a ConcurrentModificationException. Therefore, to improve the correctness of such operations, writing a program dependent on this exception is incorrect. The correct practice is: ConcurrentModificationException should only be used to detect bugs. The following uses ArrayList as an example to further analyze the causes of fail-fast.
From the above we know that fail-fast is generated when you operate the iterator. Now let's take a look at the source code of the iterator in ArrayList:
Private class Itr implements Iterator <E> {int cursor; int lastRet =-1; int expectedModCount = ArrayList. this. modCount; public boolean hasNext () {return (this. cursor! = ArrayList. this. size);} public E next () {checkForComodification ();/** omitting the code here */} public void remove () {if (this. lastRet <0) throw new IllegalStateException (); checkForComodification ();/** omitting the code here */} final void checkForComodification () {if (ArrayList. this. modCount = this. expectedModCount) return; throw new ConcurrentModificationException ();}}
From the source code, we can see that the iterator calls the checkForComodification () method when calling the next () and remove () methods. This method is mainly used to detect modCount = expectedModCount? If not, the ConcurrentModificationException is thrown, resulting in the fail-fast mechanism. So to figure out why the fail-fast mechanism is generated, we have to figure out why modCount! = ExpectedModCount: When will their values change.
ExpectedModCount is defined in Itr: int expectedModCount = ArrayList. this. modCount; so its value cannot be modified, so it is changed to modCount. ModCount is defined in AbstractList as a global variable:
protected transient int modCount = 0;
So when does it change for any reason? See the source code of ArrayList:
Public boolean add (E paramE) {ensureCapacityInternal (this. size + 1);/** omitting code here */} private void ensureCapacityInternal (int paramInt) {if (this. elementData = EMPTY_ELEMENTDATA) paramInt = Math. max (10, paramInt); ensureExplicitCapacity (paramInt);} private void ensureExplicitCapacity (int paramInt) {this. modCount + = 1; // modify modCount/** omit code here */} public boolean remove (Object paramObject) {int I; if (paramObject = null) for (I = 0; I <this. size; ++ I) {if (this. elementData [I]! = Null) continue; fastRemove (I); return true;} else for (I = 0; I <this. size; ++ I) {if (! (ParamObject. equals (this. elementData [I]) continue; fastRemove (I); return true;} return false;} private void fastRemove (int paramInt) {this. modCount + = 1; // modify modCount/** omit code here */} public void clear () {this. modCount + = 1; // modify modCount/** omit the code here */}
From the source code above, we can see that in ArrayList, no matter whether the add, remove, or clear methods involve changing the number of ArrayList elements, modCount changes. Therefore, we can preliminarily determine that the fail-fast mechanism is generated because the value of expectedModCount is not synchronized with the change of modCount. We know the root cause of fail-fast generation. We can use the following scenarios:
There are two threads (thread A and thread B). Thread A traverses the list and thread B modifies the list. When thread A traverses the list process (expectedModCount = modCount = N), the thread starts and thread B adds an element, this is because the value of modCount has changed (modCount + 1 = N + 1 ). When thread A continues to traverse and execute the next method, it notifies the checkForComodification method that expectedModCount = N, while modCount = N + 1, and the two are different. In this case, A ConcurrentModificationException is thrown, resulting in A fail-fast mechanism.
So, we have fully understood the root cause of fail-fast. If you know the reason, you can find a solution.
3. fail-fast solution
Through the previous examples and source code analysis, I think you have basically understood the fail-fast mechanism. Below I will propose a solution for the causes. There are two solutions:
Solution 1:During the traversal process, add synchronized or directly use Collections. synchronizedList to modify the modCount value. But it is not recommended because the synchronization lock caused by addition or deletion may block the traversal operation.
Solution 2:Replace ArrayList with CopyOnWriteArrayList. This solution is recommended.
Why is CopyOnWriteArrayList? A thread-safe variant of ArrayList. All variable operations (add, set, and so on) are implemented by performing a new copy on the underlying array. This class generates a large amount of overhead, but in both cases, it is very suitable for use. 1: When you cannot or do not want to perform synchronous traversal, but you need to exclude conflicts from the concurrent threads. 2: when the number of traversal operations exceeds the number of variable operations. In both cases, CopyOnWriteArrayList can be used to replace ArrayList. So why can CopyOnWriterArrayList replace ArrayList?
First, the CopyOnWriterArrayList is the same as the data structure and definition as the ArrayList. Like ArrayList, it also implements the List interface, which is implemented using arrays at the underlying layer. The add, remove, clear, and iterator methods are also included.
Second, CopyOnWriterArrayList will not generate a ConcurrentModificationException at all, that is, it will not generate a fail-fast mechanism when using the iterator. See:
Private static class COWIterator <E> implements ListIterator <E> {/** omitting the code here */public E next () {if (! (HasNext () throw new NoSuchElementException (); return this. snapshot [(this. cursor ++)];}/** omitting the code here */}
The CopyOnWriterArrayList method does not use the checkForComodification method in ArrayList to determine whether expectedModCount and modCount are equal. Why does it do this? Why? Let's take the add method as an example:
public boolean add(E paramE) { ReentrantLock localReentrantLock = this.lock; localReentrantLock.lock(); try { Object[] arrayOfObject1 = getArray(); int i = arrayOfObject1.length; Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1); arrayOfObject2[i] = paramE; setArray(arrayOfObject2); int j = 1; return j; } finally { localReentrantLock.unlock(); } } final void setArray(Object[] paramArrayOfObject) { this.array = paramArrayOfObject; }
The biggest difference between the add method of CopyOnWriterArrayList and the add method of ArrayList lies in the following code:
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);arrayOfObject2[i] = paramE;setArray(arrayOfObject2);
These three codes prevent CopyOnWriterArrayList from throwing ConcurrentModificationException. Their charm lies in copying the original array and then adding the copy array. This will not affect the array in COWIterator.
SoThe core concept of CopyOnWriterArrayList is: Any operations (such as add, remove, and clear) that change the structure of the array. CopyOnWriterArrayList copies existing data, then modify the copy data, so that the data in COWIterator will not be affected. After the modification is complete, change the reference of the original data. At the same time, the cost is to generate a large number of objects, and the copy of the array is also quite lossy.
Reference: http://www.cnblogs.com/skywang12345/p/3308762.html#a3
----- Original from: http://cmsblogs.com /? P = 1220Please respect the author's hard work and repost the source.
----- Personal site:Http://cmsblogs.com