In the collection of the JDK we often see something like this:
For example, ArrayList:
Note that the fast failure behavior of iterators is not guaranteed because, in general, it is not possible to make any hard guarantees as to whether or not there is a concurrency change in sync. A fast-failing iterator will do its best to throw concurrentmodificationexception. Therefore, it is a mistake to write a program that relies on this exception to improve the correctness of such iterators: The fast failure behavior of iterators should only be used to detect bugs.
In HashMap:
Note that the fast failure behavior of iterators is not guaranteed and, in general, there is no firm guarantee that there is an asynchronous concurrent modification. The fast-failing iterator does its best to throw concurrentmodificationexception. Therefore, it is wrong to write a program that relies on this exception, and the correct approach is that the fast failure behavior of the iterator should only be used to detect program errors.
Repeated references to "rapid failure" in these two paragraphs. So what is the "fast-failing" mechanism?
"Fast Failure", also known as Fail-fast, is an error detection mechanism for Java collections. The fail-fast mechanism is likely to occur when multiple threads are manipulating a collection for structural changes. Remember that it is possible, not necessarily. For example: Suppose there are two threads (thread 1, thread 2), thread 1 through iterator in the elements of the collection A, at some point, thread 2 modifies the structure of collection A (is the structure of the changes above, rather than simply modify the contents of the collection elements), then this time the program will throw Concurrentmodificationexception anomalies, resulting in a fail-fast mechanism.
One, fail-fast example
PublicClassfailfasttest {Privatestatic List<integer> List =New arraylist<>();/*** @desc: Thread One Iteration list * @Project: Test * @file: Failfasttest.java * @Authro: Chenssy * @data: July 26, 2014*/PrivateStaticClass ThreadoneExtendsthread{PublicvoidRun () {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 o'clock, modify list * @Project: Test * @file: Failfasttest.java * @Authro: Chenssy * @data: July 26, 2014*/PrivateStaticClass ThreadtwoExtendsthread{Publicvoid run () {int i = 0; while (i < 6if (i = = 3; }}} public static void< Span style= "color: #000000;" > main (string[] args) {for (int i = 0; i < 10;i++) {List.add (i);} new Threadone (). Start (); new Threadtwo (). Start ();}}
Operation Result:
Threadone Traversal: 0threadtwo run:0threadtwo run:1threadtwo run:2threadtwo run:3 threadtworun:4 Threadtwo run:5Exception 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
With the above examples and explanations, I initially knew that the reason for the fail-fast is that when the program iterates over the collection, a thread modifies the collection on the structure, and the iterator throws Concurrentmodificationexception exception information, resulting in fail-fast.
To understand the fail-fast mechanism, we must first understand the concurrentmodificationexception anomaly. This exception is thrown when the method detects concurrent modifications to an object, but does not allow the modification. It is also important to note that the exception does not always indicate that the object has been modified concurrently by a different thread, and that if a single thread violates the rule, it is also possible to throw a change exception.
Admittedly, the fast failure behavior of the iterator cannot be guaranteed, it cannot guarantee that the error will occur, but the fast failure operation will do its best to throw the concurrentmodificationexception exception, so It is a bad practice to write a program that relies on this exception to improve the correctness of such operations: Concurrentmodificationexception should only be used to detect bugs. Below I will take ArrayList as an example to further analyze the cause of fail-fast.
From the front we know that Fail-fast is generated when the iterator is being manipulated. Now let's take a look at the source code of the iterator in ArrayList:
PrivateClass ItrImplements Iterator<e>{IntCursorint Lastret =-1;int expectedmodcount = ArrayList.This. Modcount;PublicBooleanHasnext () {Return (This.cursor! = ArrayList.This. size); }PublicE Next () {checkforcomodification ();/**Omit the code here*/}PublicvoidRemove () {if (This.lastret < 0) throw new IllegalStateException (); Checkforcomodification (); /** Omit here code */} final voidif (Arraylist. This.modcount = = thisreturnthrow new
From the source code above, we can see that the iterator calls the next (), remove () method is called the Checkforcomodification () method, the method is mainly to detect modcount = = Expectedmodcount? If unequal then throws Concurrentmodificationexception exception, thus produces fail-fast mechanism. So to figure out why there is a fail-fast mechanism we have to use to figure out why modcount! = Expectedmodcount, when their values change.
Expectedmodcount is defined in ITR: int expectedmodcount = ArrayList.this.modCount, so his value is impossible to modify, so the change is modcount. Modcount is defined in abstractlist as a global variable:
int modcount = 0;
So when did he change for what reason? Please see the source code of ArrayList:
PublicBooleanAdd (E ParamE) {ensurecapacityinternal (This.size + 1);/**Omit the code here*/}Privatevoid Ensurecapacityinternal (IntParamint) {if (This.elementdata = =Empty_elementdata) Paramint = Math.max (10, Paramint); Ensureexplicitcapacity (Paramint); }Privatevoid Ensureexplicitcapacity (IntParamint) {This.modcount + = 1;//Modify Modcount/**Omit the code here*/}PublicBooleanRemove (Object paramobject) {IntIif (Paramobject = =Null)for (i = 0; i <This.size; ++i) {if (This.elementdata[i]! =Null)Continue; Fastremove (i);ReturnTrue; }Elsefor (i = 0; i <This.size; ++i) {if (! ( Paramobject.equals (This. Elementdata[i])))Continue; Fastremove (i);ReturnTrue; }ReturnFalseprivate void Fastremove ( int Paramint) {// modify Modcount /** Omit here code */ public Span style= "color: #0000ff;" >void Clear () {this.modcount + = 1; // modify Modcount /***/
From the above source code, we can see that the ArrayList, regardless of the add, remove, clear method as long as it involves changing the number of ArrayList elements of the method will lead to modcount changes. So here we can make a preliminary judgment because the Expectedmodcount and modcount changes are not synchronized, resulting in the difference between the two thus producing fail-fast mechanism. Knowing the root cause of the generation of fail-fast, we can have the following scenarios:
There are two threads (thread A, thread B), where thread A is responsible for traversing list, thread B modifying list. Thread A is at some point in traversing the list process (at this point expectedmodcount = modcount=n), the thread starts, and thread B adds an element, which is the value of the Modcount change (modcount + 1 = N + 1). When thread a continues to traverse the Execute next method, the advertisement Checkforcomodification method discovers Expectedmodcount = N, and Modcount = n + 1, which varies, The concurrentmodificationexception exception is thrown and the fail-fast mechanism is generated.
So, until here we have fully understood the root cause of the fail-fast. Knowing the reason will find a solution.
Iii. Fail-fast Solutions
Through the previous examples, source code analysis, I think you have a basic understanding of the mechanism of fail-fast, the following I will produce a solution to the cause. There are two types of solutions:
Scenario One: in the traversal process all involved change modcount worthwhile place all plus synchronized or directly use Collections.synchronizedlist, so you can solve. However, it is not recommended because the synchronization lock caused by additions or deletions may block the traversal operation.
Scenario Two: use copyonwritearraylist to replace ArrayList. It is recommended to use this scenario.
What is Copyonwritearraylist? A thread-safe variant of ArrayList, where all mutable operations (add, set, and so on) are implemented by a new copy of the underlying array. This class produces more overhead, but in both cases it is well suited for use. 1: When you cannot or do not want to perform synchronous traversal, but you need to exclude conflicts from concurrent threads. 2: When the number of traversal operations greatly exceeds the number of variable operations. In both cases, it is appropriate to use copyonwritearraylist instead of ArrayList. So why Copyonwriterarraylist can replace ArrayList?
First, the copyonwriterarraylist, whether from the data structure, definition and ArrayList. Like ArrayList, it implements the list interface, which is implemented using arrays. The method also includes Add, remove, clear, iterator, and so on.
Second, Copyonwriterarraylist does not produce a concurrentmodificationexception exception at all, that is, it does not produce a fail-fast mechanism with iterators at all. Please see:
PrivateStaticclass cowiterator<e> implements Listiterator <e> {/** omit the code here Span style= "color: #008000;" >*/public E next () {if ( ! (Hasnext ())) throw newreturn this.snapshot[( this.cursor++/** Omit here code */ }
The copyonwriterarraylist approach does not use the Checkforcomodification method in ArrayList to determine whether Expectedmodcount and Modcount are equal. Why would it do that, and why could it do so? Let's take the Add method as an example:
PublicBooleanAdd (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) { Span style= "color: #0000ff;" >this.array = Paramarrayofobject;
One of the biggest differences between the Add method of Copyonwriterarraylist and the Add method of ArrayList is the following three lines of code:
object[] ArrayOfObject2 = arrays.copyof (ArrayOfObject1, i + 1); Arrayofobject2[i] = Parame;setarray ( ARRAYOFOBJECT2);
This is the three lines of code that make copyonwriterarraylist not throw concurrentmodificationexception exceptions. Their charm is to copy the original array, and then add on the copy array, which does not affect the array in the Cowiterator at all.
So the core concept that Copyonwriterarraylist represents is: any operation that changes the structure of an array (add, remove, clear, etc.), Copyonwriterarraylist will copy the existing data, Then modify the copy data, so that it does not affect the data in the Cowiterator, the modification after the completion of the original data to change the reference. At the same time, the cost is to produce a large number of objects, while the copy of the array is quite lossy.
Fail-fast mechanism