“Alibaba Java Development Manual” in the first chapter of the fifth section of the seventh point is said:
Do not remove/add elements in a foreach loop. The remove element must be Iterator. If the operation is performed concurrently, the Iterator must be locked.
Here’s a counter example:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) { list.remove(item); }}Copy the code
Java forEach is an iterator. ForEach is an iterator.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("1".equals(item)) { list.remove(item); }}Copy the code
With that in mind, let’s take an ArrayList and look at its internal iterator methods:
public Iterator<E> iterator(a) {
return listIterator();
}
public ListIterator<E> listIterator(final int index) {
checkForComodification();
rangeCheckForAdd(index);
final int offset = this.offset;
return newListIterator<E>() { hasNext()... next()... . }}Copy the code
ListIterator () has too much code inside the listIterator() method, so I won’t post them all, because we’ll focus on just two methods: HasNext () and next (), then I will pass the breakpoint debugging let everybody understand why ConcurrentModificationException is occasional:
To set breakpoints
I have broken points in all three places so that we can get a rough idea of the flow:
Run the debug
P1
We can see that the first breakpoint has been located. We can also see from the information provided by IDEA that the list size is 2:
ForEach (); forEach (); haxNext(); forEach ();
Keep going, and we’ll come to the third break point:
Circle the red 1
Notice the first place I circled in red, we enter the checkForComodification:
final void checkForComodification(a) {
if(modCount ! = expectedModCount)throw new ConcurrentModificationException();
}
Copy the code
The modCount variable is the number of times the set is performed, so methods like add() and remove() can make modCount + 1, and the expectedModCount is the expected number of operations for the set. This is reset to modCount in some operations, such as add().
Since we added two elements above, modCount and modCount are both 2.
Circle the red 2
Then if we look at the second red circle, we can see that the pointer moves every time we next(), which makes sense.
P2
The breakpoint continues, because the first element is 1, so it matches:
We enter the remove() method, and since we are removing by object, we enter the second branch:
Next we go to the fastRemove() method and see modCount + 1:
P3
Further down, we’re back where we started, but if you look more closely you’ll notice that the list has changed from 2 to 1:
Then we come to hasNext() again, because the cursor and size are both 1, the loop terminates:
Eat a whale
You don’t know what to do here, huh? What about a promised error? How come there is no error?
HasNext (); hasNext(); hasNext(); hasNext(); Usually throw ConcurrentModificationException, so you don’t have fluky psychology.
Reduction of error
Here we have the same example as above, except this time I have changed the deleted object from 1 to 2:
Running debug followed by P1 and P2 is the same, so I won’t repeat it here, the only difference is in P3. Here we have reached the second loop, where element 2 is deleted after the element is validated:
On the third loop, we can see that the size of the list is 1:
Ok, let’s continue. Notice that the cursor is now 2 and the size is 1, so the loop continues:
We said previously that checkForComodification() in the next() method is to check the number of operations, so we will not repeat it here:
When we enter checkForComodification(), we can see that the modCount is 3 (because the remove() operation **modCount** is incremented) and the expectedModCount is 2, so there is an error: