1.现象还原
出现在用foreach中用集合(如arrayList)进行元素的删减
public static void test2(){
List<String> myList = new ArrayList<String>();
myList.add( "1");
myList.add( "2");
myList.add( "3");
myList.add( "4");
myList.add( "5");
for (String value : myList) {
System. out.println( "List Value:" + value);
if (value.equals( "2")) {
// error
myList.add(value);
// error
myList.remove(value);
}
}
}
List Value:1
List Value:2
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at com.example.demo.thread.multiThread.ConcurrentModification.test2(ConcurrentModification.java:51)
at com.example.demo.thread.multiThread.ConcurrentModification.main(ConcurrentModification.java:24)
Process finished with exit code 1
2.异常原因
总结:用迭代器进行循环(iterator.next()),但是用list的实现(如arraylist)进行元素的操作(add || remove),导致Iterator.next()进行check时候,exceptedModCount != ModCount;(
1.iterator每次Next()都会检查两个数是否相等(checkForComodification),不等就抛出并发修改异常
2.ArrayList的每次Add或者remove都会进行ModCount++;
3.第一次初始化时,exceptedModCount =ModCount,当进行了一次arrarList.remove()后,modCount+1,但是exceptedModCount没有变,当进行下一次Itr.Next()时,检测checkForComodification (modCount != expectedModCount)==true 就会抛出异常
注:1.exceptedModCount 为Itr的变量,是期望被修改的次数, 初始时赋值为modCount ;
2.ModCount为ArrayList的父类AbstractList的变量,记录当前的ArrayList的操作次数;
解析:
前提:1.foreach的循环:底层采用的是迭代器Iterator(Itr,这个类是ArrayLIst实现的内部类),每次循环都会调用.next()访问下一个元素。
2.普通的for循环,使用的是索引去访问数组,不会出现这个异常。
这里的Itr是ArrayList的内部类,实现了Iterator用于遍历
3.源码分析
ArrayList的remove()
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
//
fastRemove(es, i);
return true;
}
private void fastRemove(Object[] es, int i) {
//删除时会对modCount进行+1;
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
ArrayList的add()
public boolean add(E e) {
//arrayList的add也会+1
modCount++;
add(e, elementData, size);
return true;
}
内部类Itr:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
............
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
//记录预期修改次数 初始化为AbstractList的modCount;
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//检查modCount 和 expectedModCount是否相等,不等抛出ConcurrentModificationException
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];
}
..........................
}
//检测方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
总结:
可以看到,调用ArrayList的remove或者Add都会对modCount进行+1的操作(操作的时候不会对expectedModCount做任何的操作)。当remove()或者add操作完进行下一次迭代(Itr.next())时,调用checkForComodification()就会发现两个值不等,就抛出异常。
避免:
1.使用普通for循环
2.使用stream对元素操作,如过滤返回新的集合
栗子:
List<String> newCollect = myList.stream().filter(item -> !StringUtils.isEmpty(item)).collect(Collectors.toList());