ConcurrentModificationException的产生原因以及解决办法
1.问题:
在使用ArrayList、Vector、HashMap等集合类的过程中,有时会产生ConcurrentModificationException这个异常,那么这个异常是怎么产生的?如何解决?下面开始分析。
2.原因:
- 首先,查看JDK源码,看这个异常的作者对这个异常的概要的解释,了解作者为什么要设计这个异常:
/**
* This exception may be thrown by methods that have detected concurrent
* modification of an object when such modification is not permissible.
*/
也就是说,对象在个别情况下是不允许进行修改的,如果修改了,后面调用某些方法时,就会检测到,然后就直接抛出ConcurrentModificationException。
- 然后,我继续结合JDK源码,来分析这个异常一般是在什么情况下会被抛出来。其实现的原理是什么。我下面以ArrayList为例来分析:
在ArrayList里,有一个变量:
protected transient int modCount = 0;
上面这个变量表示这个集合被结构性修改的次数。什么是结构性修改?看代码就会发现,是指add()、remove()、clear()、addAll()等增、删集合元素的动作。每当执行一次这些动作,上面的modCount变量就会加一,如下面代码展示:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
那么这个变量有什么用呢?通过代码发现,在使用Iterator迭代的时候会用到这个变量,如下面代码展示(请留意中文注释):
//ArrayList里的iterator()方法
public synchronized Iterator<E> iterator() {
return new Itr();
}
/**
* 上面方法会创建这个Itr对象
* 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;
public boolean hasNext() {
// Racy but within spec, since modifications are checked
// within or after synchronization in next/previous
return cursor != elementCount;
}
public E next() {
synchronized (Vector.this) {
//校验集合是否被修改过
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
synchronized (Vector.this) {
final int size = elementCount;
int i = cursor;
if (i >= size) {
return;
}
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
while (i < size && modCount == expectedModCount)
action.accept(elementAt(es, i++));
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
//校验当前时间点此集合的修改次数是否跟本对象创建时记录的次数一致,
//如果不一致,则抛出ConcurrentModificationException异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
另外,由此我们也看到,产生ConcurrentModificationException的地方就在这里,就是在Itr的next()、remove()、forEachRemaining()里面,它们都会校验集合是否被修改,如果有修改,则抛出ConcurrentModificationException异常。校验的原理也很简单,就是对比当前的集合的修改次数与当时Itr对象创建时记录的次数,如果不一致,就是被修改过了。
因此,在实际开发过程中,如果使用Iterator来对ArrayList这种集合进行迭代的过程中,对该集合进行了增、删元素操作,然后等下一轮走到next()方法时,next方法就会检测到集合的修改,就会抛出这个ConcurrentModificationException异常。
3.解决方法:
用下面这种循环方式即可,就是不要用到Iterator来迭代:
for(int i=0;i<testVector.getVector().size();i++){
if(testVector.getVector().get(i).equals("d")){
testVector.getVector().add("i");
}
System.out.println(testVector.getVector().get(i));
}
写在最后
其实并不是所有的Iterator都会报这个ConcurrentModificationException异常。JDK作者说了,会抛这个异常的都叫fail-fast iterator。有些Iterator的具体实现类可以选择不实现这个行为。