zoukankan      html  css  js  c++  java
  • ArrayList中ConcurrentModificationException

    java中两种基本的集合结构ArrayList和LinkedList底层有两种不同的存储方式实现,ArrayList为数组实现,属于顺序存储,LinkedList为链表实现,属于链式存储,在对ArrayList做迭代删除时,会出现ConcurrentModificationException

    public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("aa");
    
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                list.remove(next);
            }
        }
    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    	at java.util.ArrayList$Itr.next(ArrayList.java:859)
    	at com.baicells.linked.list.ListTest.main(ListTest.java:23)

    但如果在list再添加一个元素,如bb,此时list.size = 2,上述代码运行结束后,list中只有bb,虽然与预期结果不一致,但并没有出现ConcurrentModificationException,当再次向集合中添加更多元素时,又出现了ConcurrentModificationException,使用的jdk版本为1.8.

    ArrayList源码中,方法iterator()返回了Itr对象

     /**
         * Returns an iterator over the elements in this list in proper sequence.
         *
         * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
         *
         * @return an iterator over the elements in this list in proper sequence
         */
        public Iterator<E> iterator() {
            return new Itr();
        }

    Itr为ArrayList内部类,主要关注hasNext,next,checkForComodification方法

     /**
         * An optimized version of AbstractList.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;
    
            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();
            }
        }

    modCount在add和remove时都会执行++操作,这个可在ArrayList源码中找到出处

    在Ite类中,将expectedModCount 的大小初始化为modCount,当执行hashNex和nex时,都不会使modCount的值发生变化

    当list中只有一个数据时,此时cursor=0,size=1,hasNex返回true,在next方法中校验modCount和expectedModCount是否相等,如果不相等,则抛出并发修改异常,如果相等,对cursor做了+1操作

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

    此时两者是相等的,都为1,然后执行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;
        }
    
        /*
         * Private remove method that skips bounds checking and does not
         * return the value removed.
         */
        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做了++操作,并且对size做了--操作,while循环继续,hasNext,cursor=1,size=0,两者不相等,则进入到循环,执行next操作

    此时modCount在上述remove操作时已经做了++操作,expectedModCount的值却没有变化,故modCount和expectedModCount是不相等的,因此抛出ConcurrentModificationException

    当输入两个参数时为什么就不会报错了,虽然结果不是预期的清空了集合?

    当集合为2时,在第一次删除后,各关键属性值分别为cursor=1,modCount在add两次后变为2,在remove一次后变为3,expectedModCount=2,size在remove后--,变为1,故此时在while循环hasNext中对比cursor!=size,返回false,while循环结束,继续向下走,所以最后集合中剩下了第二次add的结果,第一次add结果被删除,程序也没有出现ConcurrentModificationException异常。

    当输入三个参数或者更多时,会怎样?

    继续按照上述思路分析,当集合中有三个元素时,在第一次删除后,各关键属性值分别为cursor=1,modCount在add三次后变为3,在remove一次后变为4,expectedModCount=3,size在remove后--,变为2,此时while循环中cursor!=size返回true,进入while循环,next方法中检测到modCount != expectedModCount返回false,则抛出ConcurrentModificationException

    当为更多元素时,在第二次进入到next方法后,都将抛出ConcurrentModificationException,只有在数组元素个数为2时,才不会发生ConcurrentModificationException,但结果也不是我们预期的

    综上,不要在迭代集合时删除元素,即使是foreach或者普通for循环(普通for循环或者foreach也会造成size--),也不要这么做,这样做可能造成我们意想不到错误。

  • 相关阅读:
    【leetcode】面试题 01.04. 回文排列
    【leetcode】将数组分成和相等的三个部分
    【leetcode】杨辉三角
    【leetcode】杨辉三角 II
    【leetcode】判断路径是否相交
    【leetcode】路径总和
    【leetcode】山脉数组的峰顶索引
    053-4
    053-151
    053-272
  • 原文地址:https://www.cnblogs.com/qq931399960/p/10101442.html
Copyright © 2011-2022 走看看