• ConcurrentModificationException异常原因和解决方法


    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
  • 相关阅读:
    [MySQL] MySQL连接字符串总结[转]
    JavaScript删除字符串中的空格
    日期正则表达式[转]
    WIN7拥有管理员权限的使用方法
    SQLWave. GUI Client for MySQL Database Server
    Windows下MySQL多实例运行[转]
    Facebook 的 PHP 性能与扩展性[转]
    改变Datalist行背景色
    Excel导入数据库
    c#创建Table
  • 原文地址:https://www.cnblogs.com/sunny3158/p/16694564.html
Copyright © 2020-2023  润新知