Java ConcurrentModificationException异常剖析

xiaoxiao2021-02-28  35

为什么会出现ConcurrentModificationException异常?

之前在“Java Collections Framework学习笔记之Collection接口”中在介绍Iterator时有提到过ConcurrentModificationException这个异常。我们被提醒:在直接使用Iterator(不是通过增强for循环间接使用)时,要记住一个基本法则:如果对正在被迭代的集合进行结构上的改变(如add、remove、clear),那么迭代器就不再合法(会抛出ConcurrentModificationException)。 那么,我们就具体来看看这个ConcurrentModificationException异常

环境为JDK1.8

看下面一段代码

public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer integer = iterator.next(); if (integer == 2) list.remove(integer); } }

运行报错:

Exception in thread "main" java.util.ConcurrentModificationException at java.util.AbstractList$Itr.checkForComodification(Unknown Source) at java.util.AbstractList$Itr.next(Unknown Source)

我们根据上面程序的代码,一步步看ArrayList的源码: 由这一句

Iterator<Integer> iterator = list.iterator();

我们进入到ArrayList的iterator()方法的实现:

public Iterator<E> iterator() { return new Itr(); }

从这段代码看到返回的是一个Itr类型对象的引用,接下来进入Itr()看其具体实现。在JDK1.8中,ArrayList中的Itr()是AbstractList.Itr的优化版。与在AbstractList中的Itr一样,在ArrayList中的Itr也是一个内部类

private class Itr implements Iterator<E> { // 下一个要返回的元素的索引 int cursor; // 返回的最后一个元素的索引,即上一个元素的索引,如果没有返回-1 int lastRet = -1; // 对ArrayList修改次数的期望值,初始值为modCount int expectedModCount = modCount; 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: modCount = 0 modCount是AbstractList中的一个成员变量,表示对List的修改次数(每次add/remove都会使modCount+/-),其定义是protected transient int modCount = 0; - expectedModCount = 0 - cursor = 0 - lastRet = -1 - size = 1

让我们的程序继续走

while (iterator.hasNext()) {

进入到iterator的hasNext()方法中

public boolean hasNext() { return cursor != size; // }

如果下一个元素的下标和ArrayList大小不相等,说明后面还有元素。这个很好理解,如果相等了,那就是访问到最后一个元素了。 当hasNext()返回true后,进入while内部,继续跟着程序走:

Integer integer = iterator.next();

进入到iterator的next()方法,我们的程序会通过next()获取到下标为0的元素

@SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; // i = 0 if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; // cursor = 1 return (E) elementData[lastRet = i]; // lastRet = 0 }

此时变量的值为: - modCount = 0 - expectedModCount = 0 - cursor = 1 - lastRet = 0 - size = 1 继续看我们的代码

if (integer == 2) list.remove(integer);

如果当前元素值为2,则进入ArrayList的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()

private void fastRemove(int index) { modCount++; // modCount = 1 //接下来删除元素 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // 置为null让GC工作 }

第一次while循环顺利完成,此时变量的值为: - modCount = 1 - expectedModCount = 0 - cursor = 1 - lastRet = 0 - size = 0 接下来再次进入while,根据hasNext()判断cursor和size不等,继续进入循环内部,调用iterator的next()方法,在前面贴出来的next()方法中我们可以看到,会首先调用Itr类的checkForComodification()

final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

因为此时的modCount = 1,而expectedModCount = 0,则抛出ConcurrentModificationException异常

关键点就在于:list.remove()使得modCount和expectedModCount值不相等

这也就解释了开头我们提到的,为什么“对正在被迭代的集合进行结构上的改变(如add、remove、clear),那么迭代器就不再合法(会抛出ConcurrentModificationException)”

在单线程环境下解决该异常

在Itr类中也有一个remove()方法

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(); } }

这个remove方法也是调用了ArrayList的remove,但是多了一步expectedModCount = modCount; 所以删除时使用Itr的remove()就可以避免抛出ConcurrentModificationException了,不过要注意ArrayList迭代器的remove方法是不含参的,也就是说使用这个方法只能删除它刚看到的值(next()后)。

public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer integer = iterator.next(); if (integer == 2) iterator.remove(); } }
转载请注明原文地址: https://www.6miu.com/read-2625890.html

最新回复(0)