ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ConcurrentModificationException异常原因和解决方法

2022-09-14 21:34:26  阅读:316  来源: 互联网

标签:index iterator ConcurrentModificationException ArrayList elementData remove 解决 


ConcurrentModificationException异常原因和解决方法

首先看一下一个例子举出 ConcurrentModificationException 的出现场景:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<Integer>(){
        {
            add(1);
            add(2);
            add(3);
        }
    };
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        iterator.next();
        list.add(4);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

ConcurrentModificationException 中文意思就是并发修改异常,存在于并发使用 Iterator 时出现的时候,那这个异常是为什么会出现的呢?这个涉及到 fast-fail 机制(快速失败),可以提前预料遍历失败情况,防止数组越界异常,我们看一下源代码:

ArrayList 源代码

ArrayList 类的源代码

我们看一下 remove 和 add 方法:

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 值,变量类似版本
    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
}
public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //拷贝后面的值
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //添加元素
    elementData[index] = element;
    size++;
}
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
    //修改 modCount 值,变量类似版本
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

ArrayList 内部类 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() {
        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];
    }
    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();
        }
    }
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
  • lastRet:表示上一个访问的元素的索引
  • expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。

modCount 是在 ArrayList 中赋值的,并且初始值为 0,在 add 和 remove 的时候(修改元素的时候)会增加 1,


我们看一下 hasNext 方法,在方法中的 size 是 ArrayList 中的变量,这个 ConcurrentModificationException 异常存在的原因之一就在这个方法体现出来了,在多线程的情况下,如果使用迭代器遍历时,ArrayList数组元素变少导致 cursor > size,然后数组越界。

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

在正常情况下当 cursor == size代表已经到数组尽头了。我们再看一下 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];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

next 方法中先是判断了 modCount 是否等于 expectedModCount,不相等则抛出 并发修改异常,否则取到游标,然后判断游标是否大于数组长度或者元素个数。最后游标加一,然后返回数的同时使 lastRet 等于 i。我们再看一下 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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

其实就是在 remove 掉元素的时候更新下自己的 expectedModCount。注意的是remove后 lastRet 会变成 -1,也就是不能连续 remove 两次,因为在 next 中检查了 lastRet 的值不能小于 0。


解决方案

单线程的解决方案:

  • 使用 Itr 类中也给出的 remove() 方法:因为 Iterator 的 remove 会更新 expectedModCount 的值。
public static void main(String[] args) {
    List<Integer> list = new ArrayList<Integer>(){
        {
            add(1);
            add(2);
            add(3);
        }
    };
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        iterator.next();
        iterator.remove();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

多线程的解决方案:

private static List<Integer> LIST = new ArrayList<Integer>(){
    {
        add(1);
        add(2);
        add(3);
    }
};
public static void main(String[] args) {
    new Thread(() -> {
        Iterator<Integer> iterator = LIST.iterator();
        while (iterator.hasNext()) {
            iterator.next();
            iterator.remove();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    new Thread(() -> {
        try {
            Thread.sleep(400);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LIST.add(4);
    }).start();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。

原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。

因此一般有2种解决办法:

  • 在使用iterator迭代的时候使用synchronized或者Lock进行同步;

  • 使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

https://blog.csdn.net/qq_44613591/article/details/114108920

标签:index,iterator,ConcurrentModificationException,ArrayList,elementData,remove,解决,
来源: https://www.cnblogs.com/sunny3158/p/16694564.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有