public void test() {ArrayList
出现错误:
java.util.ConcurrentModificationException
at java.util.ArrayListItr.checkForComodification(ArrayList.java:909)atjava.util.ArrayListItr.checkForComodification(ArrayList.java:909) at java.util.ArrayListItr.checkForComodification(ArrayList.java:909)atjava.util.ArrayListItr.next(ArrayList.java:859)
at test.test(test.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner2.evaluate(ParentRunner.java:268)atorg.junit.runners.ParentRunner.run(ParentRunner.java:363)atorg.junit.runner.JUnitCore.run(JUnitCore.java:137)atcom.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)atcom.intellij.rt.junit.IdeaTestRunner2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner2.evaluate(ParentRunner.java:268)atorg.junit.runners.ParentRunner.run(ParentRunner.java:363)atorg.junit.runner.JUnitCore.run(JUnitCore.java:137)atcom.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)atcom.intellij.rt.junit.IdeaTestRunnerRepeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
异常产生
当我们迭代一个ArrayList或者HashMap时,如果尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的异常。
异常原因
ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素……)时都会modCount++
而foreach的背后实现原理其实就是Iterator(关于Iterator可以看Java Design Pattern: Iterator),等同于注释部分代码。在这里,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来如果集合进行修改modCount改变,就会造成expectedModCount != modCount,此时就会抛出java.util.ConcurrentModificationException异常
源码解释
1:ConcurrentModificationException异常与modCount这个变量有关。
2:modCount:modCount就是修改次数,在具体的实现类中的Iterator中才会使用。在List集合中,ArrayList是List接口的实现类,
modCount:表示list集合结构上被修改的次数。(在ArrayList所有涉及结构变化的方法中,都增加了modCount的值)
list结构上别修改是指:改变了list的长度的大小或者是遍历结果中产生了不正确的结果的方式。add()和remove()方法会是modCount进行+1操作。modCount被修改后会产生ConcurrentModificationException异常, 这是jdk的快速失败原则。
3:modCount的变量从何而来。
modCount被定义在ArrayList的父类AbstractList中,初值为0,protected transient int modCount = 0。
4:上述代码用来4个方法,操作集合,add(),next(),hasNext(),remove(),这四个方法。
(1)ArrayList中add()的源代码。
public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true;
}
在进行add()方法是,先调用的ensureCapacity()这个方法,来判断是否需要扩容。
private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}//看集合是否为空集合。很显然,并不是空集合。ensureExplicitCapacity(minCapacity); //
}
在判断是否需要扩容时,用调用ensureExplicitCapacity这个方法,注意,这个方法中对modCount进行的加一操作。
private void ensureExplicitCapacity(int minCapacity) {modCount++;if (minCapacity - elementData.length > 0)grow(minCapacity);
}
所以集合在进行添加元素是会对modCount进行+1的操作。
在进行文章开头的代码时list集合中使用add添加了元素后,size=2,modCount=2,
(2)Iterator的方法是返回了一个Itr()的类的一个实例。
public Iterator
Itr是ArrayList的一个成员内部类。
private class Itr implements Iterator
}
在for循环内部首先对Iterator进行了初始化,初始化时,expectedModCount=2,cursor=0。
(2.1)单看hasnext()方法
public boolean hasNext() {return cursor != size();
}
hasNext是判断是否右下一个元素,判断条件是cursor不等于size,有下一个元素。size=2,cursor=0,是有下一个元素的。
(2.2)单看next()方法
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];
}
当进行next()方法时,调用了checkForComodification()方法,进行判断是否抛出异常
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
此时,modCount=expectedModCount=2,不抛异常,程序继续进行,next()方法有对cursor进行了加一的操作。cursor=1。
item=“1”,继续遍历,hasNext判断是否右下一个元素,有下一个元素。size=2,cursor=1,是有下一个元素的。item=“2”,此时进行remove删除操作。
(3)ArrayList中remove的源代码。
public boolean remove(Object o) {if (o == null) {for (int index = 0; index
可以看见进行remove操作时,是通过调用fastRemove()方法进行的实际删除。在fastRemove()方法中,对modCount进行了+1的操作。
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;
}
在fastRemove()方法中,modCount进行了+1操作,modCount=3,size进行了-1的操作,size=1,程序继续进行,进行next()方法,发现modCount不等于expectedModCount(expectedModCount=2),抛出了ConcurrentModificationException异常。
解决办法:
1.单线程:
Iterator
while(iter.hasNext()){String str = iter.next();if( str.equals("B") ){iter.remove();}
}
Itr中的也有一个remove方法,实质也是调用了ArrayList中的remove,但增加了expectedModCount = modCount;保证了不会抛出java.util.ConcurrentModificationException异常。
但是,这个办法的有两个弊端
1.只能进行remove操作,add、clear等Itr中没有。
2.而且只适用单线程环境。
2.多线程环境
在多线程环境下,我们再次试验下上面的代码
public class Test2 {static List
}
Output:
异常的原因很简单,一个线程修改了list的modCount导致另外一个线程迭代时modCount与该迭代器的expectedModCount不相等。
此时有两个办法:
迭代前加锁,解决了多线程问题,但还是不能进行迭代add、clear等操作
public class Test2 {static List
}
采用CopyOnWriteArrayList,解决了多线程问题,同时可以add、clear等操作
public class Test2 {static List
}
CopyOnWriteArrayList也是一个线程安全的ArrayList,其实现原理在于,每次add,remove等所有的操作都是重新创建一个新的数组,再把引用指向新的数组。