Exception in thread "main” java.util.ConcurrentModificationException
在对list集合进行遍历,并且remove的时候,有可能会提示该异常
结论
从异常栈来看的话,异常信息是在
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法中throw的,所以根本原因是:modCount != expectedModCount
简单而言一句话:
在iteraor循环遍历中,如果直接调用list的remove方法,只会将modCount++;而不会更新expectedModCount的值
如果调用的是iterator的remove方法,除了调用list的remove方法,还会把modCount的最新值赋值给expectedModCount
源码分析
public class ConcurrentModificationExceptionDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
if("2".equals(iterator.next())){
// list.remove("2");
list.remove(iterator.next());
// iterator.remove();
}
}
System.out.println(list.size());
}
}
1、如果是list.remove(iterator.next());就会提示这个异常
2、如果是list.remove("2”); 或者 iterator.remove(); 就不会异常信息,但是这两个的原理不一样,后面再说
modCount:表示对数组的操作次数(remove、add都会+1)
expectedModCount:表示iterator对数组的操作次数(姑且先这么理解)
list.remove(iterator.next())
1.在调用这个方法的时候,会调用java.util.ArrayList#remove(java.lang.Object); 这里ArrayList的方法
这个remove方法,是根据object来从数组中依次过滤要删除的元素下标是多少,然后调用fastRemove
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
}
这里可以看到,在调用fastRemove的时候,会modCount++了;
这个方法的根本问题就是,在remove的时候,直接调用了ArrayList的remove方法,对modCount+1之后,没有同步把expectedModCount+1;
那在什么时候会报错呢?
在remove之后,再次去执行if判断的时候,if判断的时候会调用iterator.next();获取到数组的下一个值
iterator.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];
}
可以看到,再次循环的时候,执行到iterator.next()的时候,会先调用checkForComodification();这时候,modCount 值是4,expectedModCount的值是3,所以就异常了;
iterator.remove();
public void remove() {
// lastRet 这个参数可能是记录了当前遍历的元素对应的在数组中的下标(不确定,待验证)
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
这里是调用的iterator的remove方法,在方法中,又调用了list的remove方法
这里list的remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
list的remove在将数据从数组中移除之后,将modCount++;这里没什么问题,和上面的fastRemove是一样的操作,都是将数据从数组中移除
但是在iterator的remove方法中,调用了list的remove之后,会有这么一行代码
expectedModCount = modCount;
也就是:将最新一次remove操作之后的modCount值赋值给expectedModCount
所以,在下次继续遍历,调用iterator.next();此时,modCount和expectedModCount的值是一样的,就不会报错
list.remove(“2”);
前面我有说过,如果调用的是这个方法,那么在remove之后,也不会报错,但是:调用这个方法是有问题的
因为这里如果直接调用list.remove(“2”)的话,在进行下次循环的时候,会调用hasNext()判断是否需要继续遍历
public boolean hasNext() {
return cursor != size;
}
如果直接使用list.remove(“2”);这里返回的是false,就直接退出遍历了,所以这里是有问题的
size:是当前数组的个数(长度)
Cursor:个人理解是当前iterator遍历的元素下标,在每次调用iterator.next()的时候,会+1
如果在遍历的时候,里面的几个变量是有关联的:
cursor:初始值为0,每调用一次next();就会+1;remove()方法中,会把lastRet的值赋值给cursor
lastRet:初始值为-1,每调用next()的时候,会把cursor加1之前的值,赋值给lastRet;调用remove()的时候,会把改值赋值为-1
size:当前数组的长度,remove之后,会更新
第三部分这里,list.remove(“2”) 这个我没有讲清楚,后面我再整理一下这里的逻辑,更新上去
所以,在对集合进行遍历的时候,remove操作也要用iterator的,通俗的讲,就是要配套使用