ConcurrentModificationException引发原因源码简析

Java程序在遍历一个非线程安全的ListMap时,因并发修改很容易出现ConcurrentModificationException,根本原因是遍历时使用的iteratorfail-fast的,也就是说,它认为并发修改是一种不被允许的异常情况,只要出现了就应该尽快失败。

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

无论是显式地调用iterator()还是通过for-each遍历一个list,都是通过上面的Itr类,它会初始化一个expectedModCount变量,这个变量后面不会变化,除非显式地通过Itr类的remove()方法来移除元素;在next()remove()方法开始的地方,都会调用checkForComodification()是否有并发修改,当modCount != expectedModCount时就会抛出异常。

modCount是在外围类AbstractList中定义的变量,它记录了这个list结构性修改(指增加或者删除元素,文中所说的修改都是指结构性修改)的次数。当list增加或者删除元素的时候,都会有modCount++。例如下面的remove()方法(注意这里和上面remove()的区别是:不会改变iteratorexpectedModCount):

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }

综上所述:

  1. ArrayList, LinkedList, HashMap这些非线程安全的容器,在遍历时若有其他线程对容器做了结构性修改,都可能抛出ConcurrentModificationException
  2. 即使在同一个线程中,在遍历的同时如果调用容器的remove()方法来删除元素,也有可能抛出ConcurrentModificationException,正确的做法是用iteratorremove()来删除。
  3. 设计这个异常的原因是:设计者认为对非线程安全的容器的并发修改是一种非法情况,出现了就应该立即失败。对于需要并发修改的情况,应该使用线程安全的容器。

猜你喜欢

转载自blog.csdn.net/needmorecode/article/details/77427873