ConcurrentModificationException异常,for循环遍历时候, add或者remove减少集合的元素时,抛出次错误

发布时间 2023-10-24 14:44:23作者: sunny123456

ConcurrentModificationException异常

一:ConcurrentModificationException异常:

当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。

二:遍历list集合时删除元素出现的异常

  1. public static void main(String[] args) {
  2. ArrayList<String> list=new ArrayList<String>();
  3. list.add("111");
  4. list.add("222");
  5. list.add("333");
  6. for(Iterator<String> iterator=list.iterator();iterator.hasNext();){
  7. String ele=iterator.next();
  8. if(ele.equals("111")) //(1)处
  9. list.remove("222"); //(2)处
  10. }
  11. System.out.println(list);
  12. }

分析代码:这个一个在使用Iterator迭代器遍历时,同时用使用remove()方法进行了删除的操作。

当运行上述代码时,程序抛出了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()的源代码。

  1. public boolean add(E e) {
  2. ensureCapacityInternal(size + 1); // Increments modCount!!
  3. elementData[size++] = e;
  4. return true;
  5. }

在进行add()方法是,先调用的ensureCapacity()这个方法,来判断是否需要扩容。

  1. private void ensureCapacityInternal(int minCapacity) {
  2. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  3. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  4. }//看集合是否为空集合。很显然,并不是空集合。
  5. ensureExplicitCapacity(minCapacity); //
  6. }

在判断是否需要扩容时,用调用ensureExplicitCapacity这个方法,注意,这个方法中对modCount进行的加一操作。

  1. private void ensureExplicitCapacity(int minCapacity) {
  2. modCount++;
  3. if (minCapacity - elementData.length > 0)
  4. grow(minCapacity);
  5. }

所以集合在进行添加元素是会对modCount进行+1的操作。

在进行文章开头的代码时list集合中使用add添加了元素后,size=3,modCount=3,

(2)Iterator的方法是返回了一个Itr()的类的一个实例。

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

Itr是ArrayList的一个成员内部类。

  1. private class Itr implements Iterator<E> {
  2. int cursor; // cursor:表示下一个要访问的元素的索引
  3. int lastRet = -1; //
  4. int expectedModCount = modCount;
  5. public boolean hasNext() {
  6. return cursor != size;
  7. }
  8. @SuppressWarnings("unchecked")
  9. public E next() {
  10. checkForComodification();
  11. int i = cursor;
  12. if (i >= size)
  13. throw new NoSuchElementException();
  14. Object[] elementData = ArrayList.this.elementData;
  15. if (i >= elementData.length)
  16. throw new ConcurrentModificationException();
  17. cursor = i + 1;
  18. return (E) elementData[lastRet = i];
  19. }
  20. public void remove() {
  21. if (lastRet < 0)
  22. throw new IllegalStateException();
  23. checkForComodification();
  24. try {
  25. ArrayList.this.remove(lastRet);
  26. cursor = lastRet;
  27. lastRet = -1;
  28. expectedModCount = modCount;
  29. } catch (IndexOutOfBoundsException ex) {
  30. throw new ConcurrentModificationException();
  31. }
  32. }
  33. @Override
  34. @SuppressWarnings("unchecked")
  35. public void forEachRemaining(Consumer<? super E> consumer) {
  36. Objects.requireNonNull(consumer);
  37. final int size = ArrayList.this.size;
  38. int i = cursor;
  39. if (i >= size) {
  40. return;
  41. }
  42. final Object[] elementData = ArrayList.this.elementData;
  43. if (i >= elementData.length) {
  44. throw new ConcurrentModificationException();
  45. }
  46. while (i != size && modCount == expectedModCount) {
  47. consumer.accept((E) elementData[i++]);
  48. }
  49. // update once at end of iteration to reduce heap write traffic
  50. cursor = i;
  51. lastRet = i - 1;
  52. checkForComodification();
  53. }
  54. final void checkForComodification() {
  55. if (modCount != expectedModCount)
  56. throw new ConcurrentModificationException();
  57. }
  58. }

在for循环内部首先对Iterator进行了初始化,初始化时,expectedModCount=3,cursor=0。

(2.1)单看hasnext()方法

  1. public boolean hasNext() {
  2. return cursor != size();
  3. }

hasNext是判断是否右下一个元素,判断条件是cursor不等于size,有下一个元素。size=3,cursor=0,是有下一个元素的。

(2.2)单看next()方法

  1. public E next() {
  2. checkForComodification();
  3. int i = cursor;
  4. if (i >= size)
  5. throw new NoSuchElementException();
  6. Object[] elementData = ArrayList.this.elementData;
  7. if (i >= elementData.length)
  8. throw new ConcurrentModificationException();
  9. cursor = i + 1;
  10. return (E) elementData[lastRet = i];
  11. }

当进行next()方法时,调用了checkForComodification()方法,进行判断是否抛出异常

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }

此时,modCount=expectedModCount=3,不抛异常,程序继续进行,next()方法有对cursor进行了加一的操作。cursor=1。

ele=“111”。此时进行remove删除操作。

(3)ArrayList中remove的源代码。

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (int index = 0; index < size; index++)
  4. if (elementData[index] == null) {
  5. fastRemove(index);
  6. return true;
  7. }
  8. } else {
  9. for (int index = 0; index < size; index++)
  10. if (o.equals(elementData[index])) {
  11. fastRemove(index);
  12. return true;
  13. }
  14. }
  15. return false;
  16. }

可以看见进行remove操作时,是通过调用fastRemove()方法进行的实际删除。在fastRemove()方法中,对modCount进行了+1的操作。

  1. private void fastRemove(int index) {
  2. modCount++;
  3. int numMoved = size - index - 1;
  4. if (numMoved > 0)
  5. System.arraycopy(elementData, index+1, elementData, index,
  6. numMoved);
  7. elementData[--size] = null;
  8. }

在fastRemove()方法中,modCount进行了+1操作,modCount=4,size进行了-1的操作,size=2,程序继续进行,cursor=1,size=2,进行next()方法,发现modCount不等于expectedModCount,抛出了ConcurrentModificationException异常。

 

四:综上

在for循环对Iterator进行了初始化时,expectedModCount=modCount=3,cursor=0。

进行hasNext()方法,只是判断,size和cursor的值,并不改变任何值。

进行了next()方法中,先判断modCount和expectedModCount,后对cursor+1.。改变了cursor的值。

if只是找元素,并不改变值。当找到时,进行remove操作。

进行remove()方法时,modCount+1,size-1。改变了modcount和size的值。

程序会继续执行:

若是在倒数第二个元素进行删除操作时,经过前面的遍历,next()方法将cursor增加至2,只比size=3少1。在

remove()操作时,size-1=2,hasNext()发现cursor=size,程序并没有遍历最后一个元素,程序终止。

若是在倒数第2个元素之前进行删除操作,cursor<size,modCount=4,在next()方法会抛出异常。

若在最后一个元素进行删除操作,若是没有进行删除操作,cursor=size,本该在hasNext()方法中跳出for循环,进行了删除操作,modCount+1,size-1,使cursor>size,程序会继续执行next()方法,在next()方法中,判断modCount不等于expectedModCount,抛出异常。

 

五:解决方法(遍历一个集合时如何避免ConcurrentModificationException)

API文档上也有说的!   在迭代时只可以用迭代器进行删除! 

单线程情况:

(1)使用Iterator提供的remove方法,用于删除当前元素。

(2)建立一个集合,记录需要删除的元素,之后统一删除。

(3)不使用Iterator进行遍历,需要之一的是自己保证索引正常。

(4)使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnArrayList,而不是ArrayList。

多线程情况:

(1)使用并发集合类,如使用ConcurrentHashMap或者CopyOnWriteArrayList。


 
 

原文链接:https://blog.csdn.net/Jiangshan11/article/details/83038857