Java, tell me what the hell is fail fast
01. Preface
What a shame to say: a ten-year it veteran and a Java rookie. Today, I learned that there is a word "fail fast" in Java. I have to sigh that learning is really endless. As long as you are willing to learn, there will be a lot of "old" knowledge in the eyes of others, and it is all new in me.
What can we do? In addition to shame, we can only quickly devote ourselves to learning and master these knowledge.
For the sake of the town building, we must move a paragraph of English to explain fail fast.
If you don't mind, I'll translate it with poor English ability. If the political commissar finds that the commander is giving disorderly command in a campaign, he immediately reports it to the Central Military Commission with higher authority - which can effectively avoid more serious consequences. Of course, if the commander is Li Yunlong, the report is useless.
However, Li Yunlong does not exist in the world of Java. Fail fast plays the role of political commissar. Once it is reported to the superior, the subsequent actions will not be implemented.
How does it relate to code? Look at the following code.
public void test(Wanger wanger) { if (wanger == null) { throw new RuntimeException("wanger 不能为空"); } System.out.println(wanger.toString()); }
Once it is detected that Wanger is null, an exception will be thrown immediately and the caller will decide what to do in this case. Next, Wanger ToString () will not be executed -- to avoid more serious errors. This code is too simple to reflect, which will be discussed later.
Look, fail fast is the ghost. There's no mystery. If you look at the source code more, there are many examples, just like the head of the tourist peak.
And then, no? Three seconds, don't worry, let's go on.
02. Remove the set in for each
For a long time, I didn't understand why I couldn't remove elements in the for each loop. Today we'll take the opportunity to experience it.
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for (String str : list) { if ("沉默王二".equals(str)) { list.remove(str); } } System.out.println(list);
This code doesn't seem to have any problems, but it runs badly.
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.cmower.java_demo.str.Cmower3.main(Cmower3.java:14)
Why?
03. The killer mace of analyzing problems
At this time, you can only look at the source code, ArrayList The 909 lines of Java code are like this.
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
In other words, when removing, the checkforcomodification method is executed. This method compares modcount and expectedmodcount. It is found that the two are different, and a concurrentmodificationexception is thrown.
But why execute the checkforcomodification method? This requires decompiling the code for each.
List<String> list = new ArrayList(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); Iterator var3 = list.iterator(); while (var3.hasNext()) { String str = (String) var3.next(); if ("沉默王二".equals(str)) { list.remove(str); } } System.out.println(list);
It turns out that for each is realized through the iterator and the while loop.
1)ArrayList. The iterator returned by iterator () is actually an internal class ITR of ArrayList.
public Iterator<E> iterator() { return new Itr(); }
ITR implements the iterator interface.
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; Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } }
That is, when new itr(), expectedmodcount is assigned as modcount, and modcount is a member variable of list, indicating the number of times the collection has been modified. Since the list has executed the add method three times before, the value of modcount is 3; The value of expectedmodcount is also 3.
Executable list After remove (STR), the value of modcount becomes 4.
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData,index+1,elementData,index,numMoved); elementData[--size] = null; // clear to let GC do its work }
Note: the fastremove method is called inside the remove method.
The next loop executes to string STR = (string) var3 next(); When, the checkforcomodification method will be called. At this time, one is 3 and the other is 2
4. You have to throw the exception concurrentmodificationexception.
If you don't believe it, you can directly make a breakpoint and debug in line 909 of the ArrayList class.
Really, one is 4 and one is 3.
To sum up. In the for each loop, the collection traversal is actually realized through the iterator and the while loop, but the removal of the element directly uses the method of the collection class itself. This causes the iterator to find that the element has been modified without its knowledge when traversing. It finds it difficult to accept and throws an exception.
Readers, do you think I'm off the topic? What's the relationship between fail fast and the remove operation of the set in for each?
have Iterator uses the fail fast protection mechanism.
04. How to avoid the fail fast protection mechanism
Through the above analysis, I believe we all understand why we can't remove elements in the for each loop.
So how to avoid the fail fast protection mechanism? After all, deleting elements is a routine operation. We can't give up eating because of choking.
1) Break after remove
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for (String str : list) { if ("沉默王二".equals(str)) { list.remove(str); break; } }
Why am I so smart? I can't help being proud. Do some readers not understand why? Then the source code analysis above is useless. Climb the building and look again!
A little reason: after the break, the loop will no longer be traversed, which means that the next method of the iterator will no longer be executed, which means that the checkforcomodification method will no longer be executed, so the exception will not be thrown.
However, when there are duplicate elements in the list to be deleted, break is not appropriate.
2) For loop
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for (int i = 0,n = list.size(); i < n; i++) { String str = list.get(i); if ("沉默王二".equals(str)) { list.remove(str); } }
Although the for loop can avoid the fail fast protection mechanism, that is, it will not throw an exception after removing the element; However, this procedure is problematic in principle. Why?
In the first cycle, I is 0, list Size () is 3. After the remove method is executed, I is 1 and list Size () becomes 2, because the size of the list changes after remove, which means that the element of "silent king 3" is skipped. Can you understand?
List before remove Get (1) is "silent King three"; But after remove, the list Get (1) becomes "a programmer whose article is really interesting", while list Get (0) becomes "silent king 3".
3)Iterator
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); Iterator<String> itr = list.iterator(); while (itr.hasNext()) { String str = itr.next(); if ("沉默王二".equals(str)) { itr.remove(); } }
Why can the remove method of iterator avoid the fail fast protection mechanism? Just look at the source code of remove.
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(); } }
Although the remove method of ArrayList is still used to delete elements, expectedmodcount = modcount will be executed after deletion, which ensures the synchronization of expectedmodcount and modcount.
05. Last
In Java, fail fast is a collection iterator in the case of multithreading in a narrow sense. This can be seen from the definition of concurrentmodificationexception.
Translate poorly again.
The exception may be thrown because it is detected that the object is modified in a concurrent situation, and such modification is not allowed.
Usually, this operation is not allowed. For example, one thread is modifying the collection and another thread is iterating it. In this case, the result of iteration is uncertain. If this behavior is detected, some iterators (such as the internal class ITR of ArrayList) will choose to throw the exception. Such iterators are called fail fast iterators, because early failure is better than uncertain risk in the future.
Since it is for multithreading, why are our previous analysis based on single thread? In a broad sense, fail fast refers to the design of interrupting execution immediately when an exception or error occurs. It is easier to understand from the perspective of single thread.
Are you right?
The above is the whole content of this article. I hope it will help you in your study, and I hope you will support us a lot.