记一次java.util.ConcurrentModificationException异常

2019-04-26 1553 ℃

异常发生

今天运行项目,在某处调用list.size()时出现了java.util.ConcurrentModificationException异常。

然后开始在网上找资料,网上大部分结果都是说:对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。

20190426174527.png

接着我就在代码里找,有没有在迭代中删除集合元素的地方,迭代删除集合元素的地方没找到,只找到一处list.removeAll(list2),然后就怀疑是不是这里的问题,我先把这句话注释之后运行项目,发生确实没再报错,然后我就开始按照网上的方案来改。

3eda1c5d21ac0b64dc717296e2db306.png

在迭代器中如果要删除元素的话,需要调用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身上。

6d46670a5e381ffa3490da3d001cf39.png

fcda1769e76c062d84bbb1c4e6d1ebf.png

找到真正的原因

这个问题很诡异,明明删除的是其中一个集合的元素,却影响到了另外两个集合。

后面我又发现,我出现的java.util.ConcurrentModificationException和网上迭代删除时出现的又有点不一样。

ConcurrentModificationException.png

然后看代码发现,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; } }

SubLsitArrayList的内部类,它与ArrayList一样,都是继承AbstractList和实现RandomAccess接口。同时也提供了get、set、add、remove等list常用的方法。但是它的构造函数有点特殊:

  1. this.parent = parent;而parent就是在前面传递过来的list,也就是说this.parent就是原始list的引用。
  2. 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