在做项目的时候某一天遇到了ConcurrentModificationException异常。搜索了下英文的含义和网上博客的说法。想起了这个很容易忽略的问题,这里来给记录一下。
1.说一下问题发生的情景,如下代码,我们遍历ArrayList 并对符合要求的项删除。当要删除的index小于4的时候就会出现问题
List lists =newArrayList<>();
lists.add("1");
lists.add("2");
lists.add("3");
lists.add("4");
lists.add("5");
// 不正确的写法
for(String temp : lists) {
if(temp.equals("2")) {
lists.remove(temp);
}
}
2.出现的原因
ArrayList的remove方法是使用fastRemove(其源码如下),从源码中可得到ArrayList每次remove的时候其实是数组的一个copy的过程,将删除的index前后的元素重新组成一个arrayList,并且modCount++。modCount类似一个arraylist被修改的计数器。每次add或remove都会被++
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
}
看完上面在继续找原因。for(String temp: list)实质是ArrayList的内部类Itr实现的。源码如下
private class Itr implements Iterator {
int cursor;// index of next element to return
int lastRet= -1;// index of last element returned; -1 if no such
int expectedModCount=modCount;
public boolean hasNext() {
return cursor!=size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i =cursor;
if(i >=size)
throw newNoSuchElementException();
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 newConcurrentModificationException();
}
我们发现modCount!=expectedModCount 不相同时会报异常。好像找到了原因。
我们梳理一下。我们先给lists add 了五个值,modCount =5 我们要遍历元素时,expectedModCount=modCount =5 但是当我们remove的时候modCount=6,再次遍历的时候两个值不相同了抛出异常。
3.额外的实验。假如我删除最后一个会有问题吗?这个大家自己先想,最后给答案吧。
4.有了问题原因,我们来解决问题吧。
既然用样的方式不行,我们何不回归原始。来试一试for(int i =0; i<list.size;i++)来删除呢哈哈。这样就没问题了。只不过要注意i的变化,因为你remove调一个其它的会替换他的位置。我相信你明白了这个方法。
其次,我就是要用itr行不行呢?答案是可以的,继续源码ArrayList的内部类Itr有个remove方法,而且expectedModCount=modCount;重新赋值了。我去那不就是说不会在抛异常了,对的你可以使用it自身的remove而不是arrayList的remove
public void remove() {
if(lastRet<0)
throw newIllegalStateException();
checkForComodification();
try{
ArrayList.this.remove(lastRet);
cursor=lastRet;
lastRet= -1;
expectedModCount=modCount;
}catch(IndexOutOfBoundsException ex) {
throw newConcurrentModificationException();
}
}
好了,貌似现在完事了。突然有一天你发现程序在多线程的情况下还是有问题不兜圈子了,要么加上锁,要么用CopyOnWriteArrayList来替换线程不安全的ArrayList.
最后说一下之前遗留问题答案:删除最后一个元素不会抛异常。
网友评论