chatting part
Before we get to the point, let's talk about several grammatical traversal methods of the list collection in the java language
The first type: traditional for loop
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
The second: enhanced for loop
for (String value : list) {
System.out.println(value);
}
The third type: forEach
list.forEach(System.out::println);
The fourth type: stream flow
list.stream().forEach(System.out::println);
There is another kind, iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
In fact, the third and fourth types also use the iterator iterator to process when traversing the collection. However, if the iterator method is used improperly, it may cause concurrent modification exceptions. So today Xiaobai will talk about it Talk about how to avoid this problem in daily development
Concurrent modification is abnormally long like this, don’t say you don’t know it when you see it in the future
ConcurrentModificationException |
---|
Closer to home
Crime scene: see source code
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if ("12".equals(value)) {
list.remove(value);
}
}
In the above code, what we call the concurrent modification exception occurs.
As shown in the figure, we can see from the console that the culprit is in line 22 of the program, so why is this exception thrown? let's explore
First, we find the Itr class, which is an anonymous class in ArrayList and implements the Iterator interface. List.iterator() returns an instance of this class
Please look at the picture above, the class is very simple, the constructor does not do anything, three member variables
cursor: returns the index of the next element, int initialized to 0
lastRet: Returns the index of the last element, or -1 if there is no
Look at line 849, modCount is a member variable in ArrayList, indicating the number of modifications to the collection, and assign it to the expectedModCount variable
So look at the key point, in our traversal code, there seem to be only two methods, let's post them separately
hasNext()
: size is the number of elements in the collection, cursor is initially 0
public boolean hasNext() {
return cursor != size;
}
next()
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];
}
Looking at the above method, there is a checkForComodification() at the front of the element acquisition, so what does this do?
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Finally, I saw ConcurrentModificationException, which was originally thrown here, which is consistent with the exception thrown in line 22 above, just in the method of next()
It will judge whether the number of modifications of the collection is consistent with expectedModCount (expected number of modifications), and if not, an exception will be thrown
The expectedModCount variable is an instance variable of the generator, if there is an inconsistency, could it be that modCount has changed?
Can we understand that in the iterative process, modCount cannot be changed, or is expectedModCount consistent with modCount?
Does the above use the list.remove() method modify modCount? Yes
Look at the picture above, remove once, modCount will be ++, then the next time next(), expectedModCount and modCount are inconsistent
Not only remove, but also add, please do your own research
So the problem is found, how to solve it, according to our above analysis, there are three solutions?
1. modCount cannot be changed
This is simple, in the process of traversal, do not operate on the elements in the collection
2. After modCount++, the next next() is not allowed
Case code transformation: manual break
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if ("12".equals(value)) {
list.remove(value);
break;
}
}
System.out.println(list);
3. Always make expectedModCount consistent with modCount
We have operated on the elements in the collection, modCount will definitely change, and the variable expectedModCount has no exposure method and cannot be modified directly. In fact, Iterator also has a remove method
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();
}
}
Not much else to say, look at the fifth last line, the problem is solved, after deleting the element, manually let expectedModCount = modCount
Then the source code is changed to
List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String value = iterator.next();
if ("12".equals(value)) {
iterator.remove();
}
}
System.out.println(list);
epilogue
1. There are many ways to traverse the collection. When using the iterator to traverse, you cannot use the remove provided by the list to delete elements. Iterator.next() will first check whether the expectedModCount and modCount are equal. If they are not equal, a concurrent modification exception will be thrown.
2. Use the remove method provided by Iterator to delete elements, and the bottom layer will manually make expectedModCount equal to modCount.
3. ForEach and stream.forEach() will also be translated into iterators when traversing the collection, and deleting elements will also throw concurrent modification exceptions.
4. It's not easy to make, let's go with one click and three consecutive times. Your support will always be my biggest motivation!