欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ConcurrentModificationException 异常的抛出

程序员文章站 2022-04-18 17:21:46
...
ConcurrentModificationException 异常是使用java集合类经常抛出的一种异常。

这种异常常被描述为:快速失败异常,一般是我们程序错误使用导致的,很少会故意允许这种异常发生已保证逻辑上的完整。

那下面讨论什么时候会发生这种异常呢? 以ArrayList为例!

1、单线程中,一边遍历(forEach 和 list.iterator()),一遍删增数据。



List<Integer> list = new ArrayList<Integer>();
for(int i=0;i<10;i++){
list.add(i);
}
for(int i: list){ //等同 list.iterator()的效果
list.add(i);
}



深层原因 还是看看源码吧,一切因缘都在源码之中:

private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
游标 开始从0获取数据
*/
int cursor = 0;

/**
最近一次返回的数据的index,如果调用了remove ,那么返回-1
*/
int lastRet = -1;

/**
modCount 标识list结构改变的次数, modCount 是没有volitile 修饰的!!,所以在多线程环境中 这个逻辑有可能不会快速失败,
这也从另外一个方面证明:ArrayList压根就不能在线程不安全的环境中使用!!! 使用就是个错误!!

另外:expectedModCount 只有通过Iterator的 remove 方法操作 才会修改这个值,保证不会抛出ConcurrentModificationException ,
如果调用ArrayList的add 或者remove等修改底层结构的操作,那么expectedModCount != modCount 也就会发生ConcurrentModificationException 异常喽
*/
int expectedModCount = modCount;

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

public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;//当前游标也要动动哦
return next;
} catch (IndexOutOfBoundsException e) { //为什么会抛出异常? 因为:checkForComodification()之后,可能立即有其他线程变动了底层的数组结构的(多线程环境下不安全的!!)
checkForComodification(); //一旦抛出此异常,可以认定会抛出checkForComodification异常喽!
throw new NoSuchElementException();
}
}

public void remove() {
if (lastRet == -1)
throw new IllegalStateException(); //如果连续remove两次,会有问题的,这里就是证据!!
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--; //cursor 为什么会 --?? 因为:ArrayList列表lastRet删除后,发送了数组的迁移复制。
lastRet = -1;
expectedModCount = modCount; //必须的,也是只有这里才能设置 expectedModCount
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}

//检测expectedModCount 和 modCount 是否一致,如果不一致可以认定,在迭代的过程中list发生了结构的改变
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}


2、如果另外一个线程调用了remove 或者 add 或者有类似效果的操作,那么当前iterator迭代 就会抛出ConcurrentModificationException异常。也有可能不抛出,想想为什么?因为modCount 不具备线程可见性,倒是可能会抛出 IndexOutOfBoundsException 异常!!


[b]基于以上分析,在多线程环境中,上面的各自问题就更容易发生! 可能更诡异。
甚至可以说就不需要考虑多线程的情况,因为ArrayList压根就不该在多线程使用!![/b]