记录一次 Collection 集合关于 ConcurrentModificationException 的“趣事”

Iterator和并发修改异常

对Java集合类源码稍有了解的人或许都知道 Iterator和并发修改异常 的问题,即当使用 Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变(如通过调用集合的 remove 方法进行删除),
只能通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素,否则将会引发 java.util.ConcurrentModificationException 异常。


这是由于 Iterator 迭代器采用的是 快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发 `ConcurrentModificationException` 异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
/**
其实现原理大致如下:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中不断检查集合中的一个 modCount 变量。
集合在被遍历期间如果 结构 发生变化,就会改变modCount的值。
每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为迭代器的expectedmodCount值,是的话就返回遍历结果;
否则抛出ConcurrentModificationException异常,终止遍历

注意:这里异常的抛出条件是检测到 `modCount!=expectedmodCount` 这个条件。
*/

示例代码:

public class IteratorDemo {
    public static void main(String[] args) throws Exception{
        final ArrayList<String> list = new ArrayList<>();
//        final Vector<String> list = new Vector<>();
//        final LinkedList<String> list = new LinkedList<>();

        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        System.out.println(list);

        final Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            final String next = iterator.next();
            System.out.println(next);
            if ("aaa".equals(next)) list.remove(next);
        }

        System.out.println(list);
    }
}

结果显然会出错:


接下来才是这次的重点,请看如下代码:

public class IteratorDemo {
    public static void main(String[] args) throws Exception{
        final ArrayList<String> list = new ArrayList<>();
//        final Vector<String> list = new Vector<>();
//        final LinkedList<String> list = new LinkedList<>();

        list.add("aaa");
        list.add("bbb");
        //list.add("ccc"); // 唯一的区别!!!

        System.out.println(list);

        final Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            final String next = iterator.next();
            System.out.println(next);
            if ("aaa".equals(next)) list.remove(next);
        }

        System.out.println(list);
    }
}

运行结果如何呢?既然在此提出来了,那肯定不会像我们的第一直觉那样抛出异常,相反,程序‘异常’顺利的运行下去了......

或许不会有人像我这么多事,但我很好奇,这是为什么呢?而且你有兴趣的话多试试添加不同的元素个数,结果ArrayList和Vector都是只有两个元素的时候没有抛异常!!!
这不得不说有些让人费解,然而这种情形,我们也只能借助源码探究原因了(发现这个问题的当晚,状态实在不佳,请教了同事才弄明白,在此默默感谢大佬 Thanks♪(・ω・)ノ3 )


到这里就不得不再看看集合类中关于 Iterator 的实现原理了,先看看 ArrayList 的部分实现:

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

    /**
     * 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];
        }
        // ......

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

iterator() 方法会返回一个内部类,重点在于该类的 hasNext() 方法的实现,
上述示例代码中,当循环进行到第二次时,cursor 值 为 1, 而 size 也因为删除了一个元素 值也恰巧为 1 (emmm....)
hasNext() 将返回 false ......
结果显而易见了,根本运行不到检查 modCount 的 next() 方法中,这里会立即结束循环,结果就是控制台输出的样子了!!!


不知道看到这里的你什么心情,我反正已经麻了,这并不是什么有用的发现,只是对其不一致的运行结果感到有些困惑而已,强迫症没得救了...
这也再次证实了不应该在使用Iterator遍历集合时,使用非Iterator 提供的添加删除等改变结构的方法,否则不仅可能会抛出异常,还可能像该例中那样导致遍历提前结束

PS:有兴趣的你获取可以试试 Linkedlist ,其 迭代实现与 ArrayList 和 Vector 略有不同,但在次情形下最大的区别仅在 hasNext() 方法中:

public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }

    private class ListItr implements ListIterator<E> {
        // 看看这里
        public boolean hasNext() {
            return nextIndex < size;
        }
        // ......
    }

想想看 Linkedlist 会不会发生跟 ArrayList 和 Vector 类似的情况呢? 例如只有一个元素时 emmm......

posted @   itdrizzle  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示