java ArrayList遍历时会报出ConcurrentModificationException,而CopyOmWriteArrayList不会报出的原因

首先说一下,这两个集合的区别在于,一个是线程安全的,一个不是线程安全的。底层都是用数组进行存储,加一个值的时候都会进行数据拷贝。在遍历的时候,ArrayList的迭代器Itr里面有如下属性值:Itr是内部类

 	    int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

而在对ArrayList的Interator方法中是直接新建了一个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();
    }

那么也就是说新建这个迭代器的时候就进行了以下操作。

 int expectedModCount = modCount;

这两个值是什么意思呢?先看modCount,看看下面源码中的解释喽

 /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

其实就是这个集合的改变次数,你add,remove时候都需要加1的。

而expectedModCount 是在创建该迭代器时记录了改变次数。这是为了保证集合在遍历的时候被修改时将会报出异常,你会说你怎么知道,那你看看下面两串代码:

@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];
        }

上面代码是迭代器Itr的next方法,先执行checkForComodification();,这个又是什么呢,看看就知道了,

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

看见没有,每次执行next方法都会检查modCount != expectedModCount,如果多线程操作ArrayList时,你在遍历ArrayList,别人在修改ArrayList,那么极有可能抛出ConcurrentModificationException。

2、那么CopyOnWriteList事怎么避免的呢?

    /**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator provides a snapshot of the state of the list
     * when the iterator was constructed. No synchronization is needed while
     * traversing the iterator. The iterator does <em>NOT</em> support the
     * {@code remove} method.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

上面这个就是CopyOnWriteArrayList获取迭代器的方法,使用内部类COWIterator,但是你会发现在获取迭代器时使用了构造参数getArray()和0。getArray()是获取当前CopyOnWriteArrayList的元素存储的数组(CopyOnWriteArrayList底层存储元素的是数组)。那么他为啥传进去一个数组和0呢?

   /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

继续看其内部迭代器类:

static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

两个属性,cursor 游标和快照snapshot 。再看看其next()方法你就明白了,

        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }
        public boolean hasNext() {
            return cursor < snapshot.length;
        }

他能避免抛出ConcurrentModificationException的原因在于,迭代器创建的时候塞入了ArrayList的快照,这样每次迭代都只会在遍历迭代器刚创建时数组的值,对于在创建迭代器后所增加的值,是不会便利到的。你会疑问,你明明传进的是这个数组的引用啊,也就是一个地址啊。别人在增加一个元素进list的时候,你在迭代器中仍然会迭代到新加的元素啊。那你就要看看存储元素的数据是怎么来的,我们不可以看一下CopyOnWriteArrayList的add方法:

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

 

看见没有,人家存储元素的数组是在你增加元素时新建了另一个数组,跟你之前遍历的那个数组半毛钱关系都没有,因为数组地址不一样了啊

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值