首先,我也是在网上搜到的一篇博客了解到的ConcurrentModificationException的出现原因和解决办法,源地址连接https://www.cnblogs.com/dolphin0520/p/3933551.html,且文中的不少代码也是复制他的,但是一些总结都是自己的
ConcurrentModificationException
一、介绍
在前面一篇文章中提到,对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。下面我们就来讨论以下这个异常出现的原因以及解决办法。
二、目录
- ConcurrentModificationException异常出现的原因
- 在单线程环境下的解决办法
- 在多线程环境下的解决方法
三、ConcurrentModificationException异常出现的原因
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)
list.remove(integer);
}
}
}
运行后报错。
四、通过阅读源码查询原因
首先我们需要明确一点:不管是增强for还是什么,最终循环集合,都是使用的Iterator框架。
- 我们看看ArrayList.iterator()
public Iterator<E> iterator() {
return new Itr();
}
返回的是Itr对象,然后我们发现Itr是ArrayList的一个内部类,下面我只展示部分对我们有用的方法
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() {
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我们再来确认下一些变量的含义:
cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
lastRet:表示上一个访问的元素的索引
expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。
modCount:是AbstractList类中的一个成员变量,通过ArrayList的add、remove会改变他的值
第一步:hasNext()
public boolean hasNext() {
return cursor != size();
}
判断当前的索引是否是集合的最大size,如果不是则开始循环代码。如果下一个访问元素的下标等于ArrayList的大小,则肯定到达末尾了。
第二步: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];
}
因为hasNext通过后,运行next方法,首先运行checkForComodification()方法,暂时我们先不看。
- 把cursor赋值给int i ,判断 i 是否超过会等于集合的最大数量,不过是则报错。
- 把ArrayList内部持有的数组赋值给elementData,判断 i 是否大于等于数据的最大数量,如果是,则报错。
- 最后把 cursor = i +1 基本可以看成 cursor++。然后返回数组中下标为 i 的数据。并且用lastRet去记录i,也就是将i的值看成上一次的操作的下标。
第三步:回顾checkForComodification
我们回顾next()方法中的第一步checkForComodification
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
判断modCount 和 expectedModCount是否一致。
我们还必须再看Itr对象的expectedModCount变量是如何赋值的
int expectedModCount = modCount;
原来,在Itr对象一实例化的时候,就把ArrayList的modCount给了expectedModCount。意思是只要modCount一发生改变,则就会出现ConcurrentModificationException的错误。那么modCount何时发生改变,就是我们报错的至关重要的原因了。其实上文也提到了ArrayList在add、remove的时候会去改变modCount的值,也就是说只要在调用ArrayList的add、remove方法后,再去调用Itr对象的next方法,都会报错。哈哈,和海子大神总结的一样哎。
顺着add方法找下去会找到modCount++;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
顺着remove方法找下去会找到modCount++;
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 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
}
五、在单线程环境下的解决办法
其实很简单,细心的朋友可能发现在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();
}
}
根据ArrayList.this.remove(lastRet);
其实调用的还是ArrayList的remove的方法,只是多了一步:expectedModCount = modCount;
也就是每次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(); //注意这个地方
}
}
}
//成功
六、在多线程环境下的解决方法
上面的解决办法在单线程环境下适用,但是在多线程下适用吗?看下面一个例子:
public class Test {
static ArrayList<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Thread thread1 = new Thread(){
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
};
Thread thread2 = new Thread(){
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove();
}
};
};
thread1.start();
thread2.start();
}
}
报错原因:
有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。
原因在于:你在2个线程中都有这么一个代码Iterator<Integer> iterator = list.iterator();
从JVM的眼光看,2个线程,2个局部变量,则两个线程中的iterator是2个对象,那么iterator中的expectedModCount也是2个不同的,但是这2个expectedModCount都需要和同一个ArrayList的modCount进行对比,那么线程1在循环遍历,线程2在iterator.remove,则线程2的expectedModCount=1能和list的modCount=1保持一致,但是线程1的expectedModCount=0却不能和list的modCount=1保持一致,则报错
解决办法
- 使用synchronized保证有序性,将线程串行的执行,则将多线程强转为单线程,在使用iterator的remove方法则可以解决。
- 使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。