ArrayList中ConcurrentModificationException
java中两种基本的集合结构ArrayList和LinkedList底层有两种不同的存储方式实现,ArrayList为数组实现,属于顺序存储,LinkedList为链表实现,属于链式存储,在对ArrayList做迭代删除时,会出现ConcurrentModificationException
public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("aa"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String next = iterator.next(); list.remove(next); } }
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.baicells.linked.list.ListTest.main(ListTest.java:23)
但如果在list再添加一个元素,如bb,此时list.size = 2,上述代码运行结束后,list中只有bb,虽然与预期结果不一致,但并没有出现ConcurrentModificationException,当再次向集合中添加更多元素时,又出现了ConcurrentModificationException,使用的jdk版本为1.8.
ArrayList源码中,方法iterator()返回了Itr对象
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); }
Itr为ArrayList内部类,主要关注hasNext,next,checkForComodification方法
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") 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]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
modCount在add和remove时都会执行++操作,这个可在ArrayList源码中找到出处
在Ite类中,将expectedModCount 的大小初始化为modCount,当执行hashNex和nex时,都不会使modCount的值发生变化
当list中只有一个数据时,此时cursor=0,size=1,hasNex返回true,在next方法中校验modCount和expectedModCount是否相等,如果不相等,则抛出并发修改异常,如果相等,对cursor做了+1操作
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
此时两者是相等的,都为1,然后执行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; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ 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做了++操作,并且对size做了--操作,while循环继续,hasNext,cursor=1,size=0,两者不相等,则进入到循环,执行next操作
此时modCount在上述remove操作时已经做了++操作,expectedModCount的值却没有变化,故modCount和expectedModCount是不相等的,因此抛出ConcurrentModificationException
当输入两个参数时为什么就不会报错了,虽然结果不是预期的清空了集合?
当集合为2时,在第一次删除后,各关键属性值分别为cursor=1,modCount在add两次后变为2,在remove一次后变为3,expectedModCount=2,size在remove后--,变为1,故此时在while循环hasNext中对比cursor!=size,返回false,while循环结束,继续向下走,所以最后集合中剩下了第二次add的结果,第一次add结果被删除,程序也没有出现ConcurrentModificationException异常。
当输入三个参数或者更多时,会怎样?
继续按照上述思路分析,当集合中有三个元素时,在第一次删除后,各关键属性值分别为cursor=1,modCount在add三次后变为3,在remove一次后变为4,expectedModCount=3,size在remove后--,变为2,此时while循环中cursor!=size返回true,进入while循环,next方法中检测到modCount != expectedModCount返回false,则抛出ConcurrentModificationException
当为更多元素时,在第二次进入到next方法后,都将抛出ConcurrentModificationException,只有在数组元素个数为2时,才不会发生ConcurrentModificationException,但结果也不是我们预期的
综上,不要在迭代集合时删除元素,即使是foreach或者普通for循环(普通for循环或者foreach也会造成size--),也不要这么做,这样做可能造成我们意想不到错误。