记一次java.util.ConcurrentModificationException异常
记一次java.util.ConcurrentModificationException异常
2019-04-26 2106 ℃
异常发生
今天运行项目,在某处调用list.size()
时出现了java.util.ConcurrentModificationException
异常。
然后开始在网上找资料,网上大部分结果都是说:对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException
异常。
接着我就在代码里找,有没有在迭代中删除集合元素的地方,迭代删除集合元素的地方没找到,只找到一处list.removeAll(list2)
,然后就怀疑是不是这里的问题,我先把这句话注释之后运行项目,发生确实没再报错,然后我就开始按照网上的方案来改。
在迭代器中如果要删除元素的话,需要调用Itr类的remove方法。
解决方案提供的代码如下:
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(); //注意这个地方
}
}
}
改完之后,却发现,依然出现同样的异常,list.removeAll(Collection<?> c);
是JDK里的方法,也确实不应该会在删除过程中出现异常。然后debug发现,异常并不是发生在list,而是发生在和操作list处于同一map
中的其他list
身上。
找到真正的原因
这个问题很诡异,明明删除的是其中一个集合的元素,却影响到了另外两个集合。
后面我又发现,我出现的java.util.ConcurrentModificationException
和网上迭代删除时出现的又有点不一样。
然后看代码发现,map
中的集合都是通过调用list.subList(int fromIndex, int toIndex);
获得的,subList(int fromIndex, int toIndex)
的源代码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
subListRangeCheck
方式是判断fromIndex、toIndex是否合法,如果合法就直接返回一个subList
对象,注意在产生该new该对象的时候传递了一个参数 this ,该参数非常重要,因为他代表着原始list。
/**
* 继承AbstractList类,实现RandomAccess接口
*/
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent; //列表
private final int parentOffset;
private final int offset;
int size;
//构造函数
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
//set方法
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
//get方法
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
//add方法
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
//remove方法
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
}
SubLsit
是ArrayList
的内部类,它与ArrayList
一样,都是继承AbstractList
和实现RandomAccess
接口。同时也提供了get、set、add、remove等list常用的方法。但是它的构造函数有点特殊:
this.parent = parent;
而parent就是在前面传递过来的list,也就是说this.parent
就是原始list的引用。this.offset = offset + fromIndex;this.parentOffset = fromIndex;
。同时在构造函数中它甚至将modCount(fail-fast机制)传递过来了。
通过代码我们可以发现,subList
方法返回的SubList
同样也是AbstractList
的子类,同时它的方法如get、set、add、remove等都是在原列表上面做操作,它并没有像subString
一样生成一个新的对象。所以subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上。
由此可见,问题就是出现在这上面了,我对通过subList
方法生成的其中一个list
进行了元素删除操作,而map
中存储的这几个SubList
引用的又是同一个list
对象,不出问题才怪。
解决问题
找到了真正的原因,解决起来自然也不是难事,解决方案有很多种,我选择了最简单粗暴的方式:new一个新的ArrayList来存储subList方法返回的集合数据。
使用SubList
还有一点需要注意:subList生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常。
文章链接:http://www.iszhouhua.com/concurrent-modification-exception.html
发表时间:2019-04-26 21:46
最后更新时间:2019-05-30 21:36
0条评论