java.util.ConcurrentModificationException异常原因和解决方法

一、前言

最近项目中遇到一个异常问题,需求是转变数据库中的格式,需要循环每个list并对其进行add或remove长度增减操作,代码编译没问题,但运行后系统报错:

java.util.ConcurrentModificationException: null
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429) ~[na:1.8.0_77]
    at java.util.HashMap$EntryIterator.next(HashMap.java:1463) ~[na:1.8.0_77]
    at java.util.HashMap$EntryIterator.next(HashMap.java:1461) ~[na:1.8.0_77]

于是决定记录下问题,总结原因和解决方法。

二、问题原因

在ArrayList的父类AbstractList的源码中有iterator()迭代方法,其中实现为返回一个新建Itr()对象:

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

继续进入Itr()方法,AbstractList中一个内部成员类:

 protected transient int modCount = 0;
   
 private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

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

在这个具体实现类中,有以下四个成员变量:

  • cursor:后续调用next返回元素的索引;
  • lastRet:最近调用返回的元素的索引,如果调用remove删除该元素,则重置为-1;
  • expectedModCount:对ArrayList修改次数的期望值,初始值为modCount;
  • modCount:list的修改次数。调用add()和remove()方法就会对modCount进行加一操作

当调用递归方法时,总是会先调用checkForComodification()方法,然后根据cursor的值获取到元素,之后将cursor的值付给lastRet,并对cursor的值进行加一操作。初始时,cursor=0,lastRet=-1,那么调用一次之后,cursor=1,lastRet=0。注意此时,modCount=0,expectedModCount=0。

而在remove()方法中则是对modCount进行加一操作。然后接下来就是删除元素的操作,最后将size进行减一操作,并将引用置为null以方便垃圾收集器进行回收工作。那么注意此时各个变量的值:对于iterator,其expectedModCount=0,cursor=1,lastRet=0。对于list,其modCount=1,size=0。接着看程序代码,执行完删除操作后,继续while循环,调用hasNext()方法判断,由于此时cursor=1,而size=0,那么返回true,所以继续执行while循环,然后继续调用iterator的next()方法。

如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。很显然,此时modCount=1,而expectedModCount=0,因此程序就抛出了ConcurrentModificationException异常。因为在递归中调用list.remove()方法导致modCount和expectedModCount的值不一致。

 

三、解决方法

    public ListIterator<E> listIterator(final int index) {
        rangeCheckForAdd(index);

        return new ListItr(index);
    }


    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

通过查看源码可以发现在AbstractList类中调用listIterator()方法,其底层ListItr()的add()方法中expectedModCount = modCount;这时 checkForComodification()方法就不会再报错。所以如果业务场景中需要迭代对list进行增删元素时,使用ListIterator对象,调用他的remove()或add()方法即可。

 

参考资料

Java ConcurrentModificationException异常原因和解决方法

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值