Question:
ConcurrentModificationException problem appear in the List or the forEach Iterator loop, called operation data in the List. Such as:
//List Iterator Iterator
public static void main(String[] args) {
ArrayList<int> list=new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator=list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
if(str.equals("a"){
list.remove("a");
}
}
System.out.println(list);
}
//List forEach loop to manipulate data
public static void main(String[] args) {
ArrayList<int> list=new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for(String str : list){
if(str.equals("a"){
list.remove("a");
}
}
System.out.println(list);
}
Copy the code
The above two situations will lead to ConcurrentModificationException, so why is this?
The reason:
List creates an internal Itr class to implement the Iterator interface. Therefore, ArrayList forEach will not distinguish between the two cases. Knowing that forEach is actually looping through Itr, let’s take a look at this class.
private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;
// Cursor for the first element
int cursor;
// The cursor of the previous element
int lastRet = -1;
// expectedModCount: A List modification record maintained by Itr
// modCount: List Maintains a List modification record
int expectedModCount = modCount;
public boolean hasNext(a) {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next(a) {
if(modCount ! = expectedModCount)throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return(E) elementData[lastRet = i]; }... The rest of the code is omitted.... }Copy the code
ForEach is the implementation of Iterator. The hasNext() method is used to determine if there is still data to iterate over, and the key is in the next() method. This method is used to obtain the next element, but before obtaining it, a judgment is made that the Itr modification record (expectedModCount) must be equal to the List modification record (modCount) to continue, otherwise the data source has been modified. The next element is no longer accurate. Let’s look at how variables maintained by two classes can be inconsistent. (Take the remove() method as an example)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true; }}else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true; }}return false;
}
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
}
Copy the code
As you can see from the above code, the remove() method calls the fastRemove() method to delete data and add the modCount maintained by the List +1, but the modCount maintained by the Itr (expectedModCount) does not change. The List forEach loop is implemented using the List inner Itr class. The Itr maintains a List change record variable, expectedModCount, and the List maintains a change record variable, modCount. If you call the operation data method of the List class in a forEach or Iterator loop, then only modCount is modified, and when the next element of the loop is fetched (calling Itr’s next() method), Due to expectedModCount and modCount have not equal, throw ConcurrentModificationException, to tell you the List has been changed, the access to the elements in the List is not accurate.
Solutions:
- Manipulate data using methods in the Itr
- Instead of Iterator or forEach loops, use the for loop to maintain the index yourself
- Use and distribute the collection classes provided by the package to store and process data, such as CopyOnArrayList