增强for循环引发并发修改异常的原理分析

增强for循环(迭代器)引发并发修改异常

ConcurrentModificationException是RuntimeException的一个子类,一般在多线程操作没有线程安全机制的集合对象的时候会触发这个异常(如ArrayList,HashMap等等)

需要特别注意的是,在进行集合的增强for循环遍历时,在增强for循环内部,通过集合的引用直接操作集合也会触发这个异常。

//对集合进行操作引发并发修改异常
public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        for (Integer integer : list) {
            list.add(4);
        }
    }

并发修改异常的起因(以ArrayList为例进行分析)

我们查看ArrayList代码发现所有的ConcurrentModificationException异常的抛出基本都和modCount这个参数相关,

增强for循环的原理

增强for循环本质是一个语法糖,它是对迭代器书写的简化,底层使用的还是迭代器进行遍历。

ArrayList中的迭代器

在ArrayList的第848行开始,定义了一个迭代器的内部类。

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;

        Itr() {}

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

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
           }

我们可以看出在你通过ArrayList对象获取到迭代器的时候,迭代器对象会初始化一个expectedModCount 变量,这个变量值等于ArrayList中的modCount值。

直接通过ArrayList对象直接操作集合的时候,modCount值就会发生相应的改变。
但是expectedModCount值不会改变。

再次使用迭代器获取下一个元素的时候
next()方法会调用checkForComodification方法
这时候,expectedModCount 还是原来的值,
但是modCount已经发生了改变
if (modCount != expectedModCount)成立
ConcurrentModificationException便会抛出

这也就是增强for循环触发ConcurrentModificationException的核心原理

验证

这里我们可以进行简单的验证工作 -------如果使用ArrayList对象直接修改集合,没有改变modCount值,还会不会引发并发修改异常

因为modCount这个参数是私有的,ArrayList也没有给可以操作他的方法,
所以自然想到了反射获取modCount变量
附带测试源码如下

public static void main(String[] args) throws IllegalAccessException {

        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        //获取ArrayList类对象
        Class listClass = list.getClass();
        //通过ArrayList获取AbstractList类对象
        Class superclass = listClass.getSuperclass();

        try {
            //通过AbstractList类获取到modCount字段
            Field modCountField = superclass.getDeclaredField("modCount");
            //设置该字段可访问
            modCountField.setAccessible(true);

            //设置删除标志位
            boolean flag = true;
            for (Integer integer : list) {
                //每次遍历如果都进行删除操作,会引起NoSuchElementException
                if (flag) {

                    list.remove(1);
                    flag = false;
                }
                //list.remove导致modCount加一,现在通过modCountField,将它还原,就不再引起并发修改异常
                modCountField.set(list, 5);
                System.out.println(integer);


            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

运行发现并发修改异常没有发生,但是将modCountField.set(list, 5);注释之后,并发修改异常又会发生。

这里需要说明的是,我们在进行迭代器遍历集合的时候,是不能通过集合引用对集合进行操作的,这是写java程序的一个基本原则,上面的测试用例,只是验证原理,没有任何实际用途。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值