zoukankan      html  css  js  c++  java
  • 集合List之ConcurrentModificationException异常分析

    一、前言  

      Java中,集合类ArrayList不管是在开发工作中,还是在面试中,都应该是个比较高频出现的知识点。在使用过程中,可能会遇到迭代删除的需求场景,此时如果代码书写不当,极有可能会抛出 java.util.ConcurrentModificationException 异常信息。下面对这个异常做点分析,为什么会出现异常,怎样去正确的迭代删除。

    二、异常原因分析

      测试代码如下:

    package com.cfang.prebo.oTest;
    
    import java.util.Iterator;
    import java.util.List;
    
    import com.alibaba.fastjson.JSON;
    import com.google.common.collect.Lists;
    
    public class TestListException {
    
    	public static void main(String[] args) {
    		List<Integer> list = Lists.newArrayList();
    		list.add(1);
    		Iterator<Integer> iterator = list.iterator();
    		while(iterator.hasNext()) {
    			Integer val = iterator.next();
    			if(val == 1) {
    				list.remove(val);
    //				iterator.remove();
    			}
    		}
    		System.out.println("result:" + JSON.toJSONString(list));
    	}
    }
    

      运行结果:

      从异常栈信息中可以看出,最终抛出此异常的方法,是 checkForComodification 方法。下面进行追根逐源的看看,为什么方法会抛出异常。

      首先整体贴出迭代器 Iterator 对 List 进行迭代的关键性代码片段:

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

      对List的迭代iterator会new出个Itr对象的引用,Itr是个成员内部类实现。其中几个关键性的属性:

        cursor - 游标索引,表示下一个可访问的元素的索引

        lastRet - 还是索引,是上一个元素的索引。默认值-1

        expectedModCount - 对集合的修改期望值,初始值等于modCount

        modCount的定义在AbstractList中,初始值为0,如下定义:

    protected transient int modCount = 0;
    

        该值会在List的方法add以及remove中,进行加1操作,如下代码片段:

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

      

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    private void ensureCapacityInternal(int minCapacity) {
            if (elementData == EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

      也就是说,在进行add和remove的时候,都会将modCount值修改。

      好了,铺垫到这里,就可以来结合测试main方法来进行一步步的解释说明了:

        1、初始化ArrayList,调用list.add方法,此时,modCount=1,list.size = 1

        2、初始化itreator迭代循环。此时,expectedModCount = modCount = 1,cursor默认值0,lastRet默认值-1

        3、itreator.hasNext方法判断,cursor != size成立,有元素可访问,进入循环

        4、调用itreator.next方法,校验后获取值。此时expectedModCount == modCount成立,校验通过。获取值并设置相关属性 lastRet = 0,cursor = 1

        5、调用list.remove方法,modCount加1。此时,modCount=2,list.size = 0

        6、itreator.hasNext方法判断,cursor != size成立,进入循环

        7、调用itreator.next方法,校验方法checkForComodification,此时,expectedModCount != modCount成立,抛出ConcurrentModificationException异常

      写到这里,基本上为啥会出现异常,应该是已经非常明了清晰了。总结起来就是:如果是使用list.remove的话,会导致expectedModCount != modCount条件成立,也即两个的值会不等。当然了,使用for-each迭代也是一样的,毕竟for-each底层如果是对集合遍历的话,也还是利用itreator去做的。

      说完原因呢,下面简单说说解决办法:

      单线程情况下:可以使用迭代器itreator提供的remove,从源码中可以看出,在方法中会对cursor、lastRet重设值,将expectedModCount重新设值为modCount。

      多线程情况下:1、迭代删除使用锁 - synchronized或者lock

             2、创建安全的容器 - Collections.synchronizedList方法、CopyOnWriteArrayList

  • 相关阅读:
    题解 P1003 【铺地毯】
    题解 P1000 【超级玛丽游戏】
    题解 P1036 【选数】
    题解 P1217 【[USACO1.5]回文质数 Prime Palindromes】
    题解 AT934 【完全数】
    题解 P2044 【[NOI2012]随机数生成器】
    题解 P4277 【河城荷取的烟花】
    System.Linq.Dynamic.Core
    008 TBS Studio 真机开发辅助工具的简单说明
    webpack
  • 原文地址:https://www.cnblogs.com/eric-fang/p/11320093.html
Copyright © 2011-2022 走看看