Java ConcurrentModificationException并发修改异常发生的原因及解决方法

异常发生的场景

for (DataBean dataBean : dataBeanList) {
    dataBeanList.remove(dataBean);
}

异常发生在ArrayList.java:860

image
public E next() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    int i = cursor;
    if (i >= limit)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
            throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = I];
}

可以看到当modCountexpectedModeCount不相等时会抛出该异常,前者代表对集合的修改次数,后者对代表修改次数的期望值,期望两个变量是相等的

那么当执行remove操作时发生了什么呢

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;
}

进入fastRemove(index)

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
}

可以看到在进行remove时,同时对modCount的值进行了修改,这就是异常发生的原因所在

其实无论通过for infor each内部都是通过迭代器的方式遍历的,那么就免不了执行迭代器的next()方法,而迭代器的next()方法会对modCount进行判断,因此会抛出异常

在List中,无论是add()remove()等操作,都会对modCount进行修改

使用普通循环遍历,不会抛出异常

for (int i = 0; i < dataBeanList.size(); i++) {
    DataBean dataBean = dataBeanList.get(i);
    dataBeanList.remove(dataBean);
                
}

但是这种方法是错误的,在删除集合中元素时,集合的大小发生了变化,元素的索引也发生了变化,比如循环到第二个元素时,将它删除了,接下来访问第三个元素时,实际访问的是第四个元素。因此,会发生删除不全的情况,还有可能出现数组下标越界的异常,强烈不推荐使用

解决方法

单线程环境

  • 使用迭代器遍历,并使用迭代器提供的方法
Iterator dataBeanIterable = dataBeanList.iterator();
    while (dataBeanIterable.hasNext()){
    DataBean dataBean = dataBeanIterable.next();
    dataBeanIterable.remove();
}

迭代器内部提供了remove()方法

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
        limit--;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

可以看到在最后进行了expectedModCount = modCount处理

多线程环境

  • 加锁

synchronized或者lock

  • 使用安全的容器

Collections.synchronizedList方法或使用CopyOnWriteArrayList

总结

在遍历集合的元素进行修改操作时应使用迭代器内提供的方法,这对ListMap同样适用

如有错误,还请指出

你可能感兴趣的:(Java ConcurrentModificationException并发修改异常发生的原因及解决方法)