异常发生的场景
for (DataBean dataBean : dataBeanList) {
dataBeanList.remove(dataBean);
}
异常发生在ArrayList.java:860
public E next() {
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];
}
可以看到当modCount
和expectedModeCount
不相等时会抛出该异常,前者代表对集合的修改次数,后者对代表修改次数的期望值,期望两个变量是相等的
那么当执行remove
操作时发生了什么呢
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;
}
进入fastRemove(index)
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
}
可以看到在进行remove
时,同时对modCount
的值进行了修改,这就是异常发生的原因所在
其实无论通过for in
,for each
内部都是通过迭代器的方式遍历的,那么就免不了执行迭代器的next()
方法,而迭代器的next()
方法会对modCount
进行判断,因此会抛出异常
在List中,无论是add()
、remove()
等操作,都会对modCount
进行修改
使用普通循环遍历,不会抛出异常
for (int i = 0; i < dataBeanList.size(); i++) {
DataBean dataBean = dataBeanList.get(i);
dataBeanList.remove(dataBean);
}
但是这种方法是错误的,在删除集合中元素时,集合的大小发生了变化,元素的索引也发生了变化,比如循环到第二个元素时,将它删除了,接下来访问第三个元素时,实际访问的是第四个元素。因此,会发生删除不全的情况,还有可能出现数组下标越界的异常,强烈不推荐使用
解决方法
单线程环境
- 使用迭代器遍历,并使用迭代器提供的方法
Iterator dataBeanIterable = dataBeanList.iterator();
while (dataBeanIterable.hasNext()){
DataBean dataBean = dataBeanIterable.next();
dataBeanIterable.remove();
}
迭代器内部提供了remove()
方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
可以看到在最后进行了expectedModCount = modCount
处理
多线程环境
- 加锁
synchronized
或者lock
- 使用安全的容器
Collections.synchronizedList
方法或使用CopyOnWriteArrayList
总结
在遍历集合的元素进行修改操作时应使用迭代器内提供的方法,这对List
、Map
同样适用
如有错误,还请指出