zoukankan      html  css  js  c++  java
  • java.util.ConcurrentModificationException异常分析

    Java在操作ArrayList、HashMap、TreeMap等容器类时,遇到了java.util.ConcurrentModificationException异常。以ArrayList为例,如下面的代码片段:

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class Test {
    
        public static void main(String[] args){
            List<String> strList = new ArrayList<String>();
            strList.add("string1");
            strList.add("string2");
            strList.add("string3");
            strList.add("string4");
            strList.add("string5");
            strList.add("string6");
            
            // 操作方式1:while(Iterator);报错
            Iterator<String> it = strList.iterator();
            while(it.hasNext()) {
                String s = it.next();
                if("string2".equals(s)) {
                    strList.remove(s);
                }
            }
            
            // 解决方案1:使用Iterator的remove方法删除元素
            // 操作方式1:while(Iterator):不报错
    //        Iterator<String> it = strList.iterator();
    //        while(it.hasNext()) {
    //            String s = it.next();
    //            if("string2".equals(s)) {
    //                it.remove();
    //            }
    //        }
            
            // 操作方式2:foreach(Iterator);报错
    //        for(String s : strList) {
    //            if("string2".equals(s)) {
    //                strList.remove(s);
    //            }
    //        }
            
            // 解决方案2:不使用Iterator遍历,注意索引的一致性
            // 操作方式3:for(非Iterator);不报错;注意修改索引
    //        for(int i=0; i<strList.size(); i++) {
    //            String s = strList.get(i);
    //            if("string2".equals(s)) {
    //                strList.remove(s);
    //                strList.remove(i);
    //                i--;// 元素位置发生变化,修改i
    //            }
    //        }
            
            // 解决方案3:新建一个临时列表,暂存要删除的元素,最后一起删除
    //        List<String> templist = new ArrayList<String>();
    //        for (String s : strList) {
    //            if(s.equals("string2")) {
    //                templist.add(s);
    //            }
    //        }
    //        // 查看removeAll源码,其使用Iterator进行遍历
    //        strList.removeAll(templist);
            
            // 解决方案4:使用线程安全CopyOnWriteArrayList进行删除操作
    //        List<String> strList = new CopyOnWriteArrayList<String>();
    //        strList.add("string1");
    //        strList.add("string2");
    //        strList.add("string3");
    //        strList.add("string4");
    //        strList.add("string5");
    //        strList.add("string6");
    //        Iterator<String> it = strList.iterator();
    //        while (it.hasNext()) {
    //            String s = it.next();
    //             if (s.equals("string2")) {
    //                 strList.remove(s);
    //            }
    //        }
            
        }    
    }

    执行上述代码后,报错如下:

    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
        at java.util.ArrayList$Itr.next(Unknown Source)
        at concurrentModificationException.Test.main(Test.java:21)

    在第21行报错,即it.next(),迭代器在获取下一个元素时报错。找到java.util.ArrayList第830行,看到it.next()的源代码,如下:

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

    调用了checkForComodification()方法,代码如下:

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

    即,比较modCount和expectedModCount两个是否相等。modCount是ArrayList从AbstractList继承来的属性,查看modCount属性的doc文档,可知,modCount表示列表(list)被结构性修改(structurally modified)的次数。structurally modified是指造成列表中元素个数发生变化的操作,ArrayList中的add,addAll,remove, fastRemove,clear, removeRange,  ensureCapacity,  ensureCapacityInternal,  ensureExplicitCapacity等方法都会使modCount加1,而batchRemove,removeAll,retainAll等方法则根据删除的元素数增加modCount的值(removeAll和retainAll都是调用batchRemove实现,具体modCount的修改算法还需研究)。

    第一个代码片段中的操作方式1和操作方式2都是采用了迭代器的方式。当使用iterator方法得到Iterator对象(或者使用listIterator获得ListItr对象),其实是返回了一个Iterator接口的实现类ArrayList$Itr(继承自AbstractList$Itr),该类为ArrayList的一内部类,该类中有一个expectedModCount字段,当调用ArrayList$Itr的next方法时,会先检查modCount的值是否等于expectedModCount的值(其实在调用next, remove, previous, set, add等方法时都会检查),不相等时就会抛出java.util.ConcurrentModificationException异常。这种现象在java doc中称作fail-fast。

    为什么会抛出该异常呢?从代码可以看到调用了6次add方法,这时modCount的值也就为6,当当使用iterator方法得到Iterator对象时把modCount的值赋给了expectedModCount,开始时expectedModCount与modCount是相等的,当迭代到第二个元素(index=1)“string2”时,因为if条件为true,于是又调用了remove方法,调用remove方法时modCount值又加1,此时modCount的值为7了,而expectedModCount的值并没有改变,当再次调用ArrayList$Itr的next方法时检测到modeCount与expectedModCount不相等了,于是抛出异常。

    当把if语句写成if(s.equals("string5"))时又没有抛出该异常,这又是为什么呢?ArrayList$Itr中还有一个名为cursor的字段用来指向迭代时要操作的元素索引,初始值为0,每调用一次next方法该字段值加1,注意是先从集合中取出了元素再加1的。当判断"string5"时,注意是倒数第二个元素,这些cursor的值为5,移除掉元素"string5"时,List的size为5,当调用ArrayList$Itr的hasNext方法判断有无下一个元素时,判断的依据为cursor的值与size是否相等,不相等则还有下一个元素,而此时两者值刚好相等,也就没有往下执行next方法了,也就没有抛出异常,因此删掉倒数第二个元素时不会抛异常的异常。

    解决方案有四种,直接看第一段代码即可。

     

    参考链接:

    http://blog.csdn.net/xtayfjpk/article/details/8451178

    《多线程情况下只能使用CopyOnWriteArrayList》http://www.2cto.com/kf/201403/286536.html

  • 相关阅读:
    bzoj3505 数三角形 组合计数
    cogs2057 殉国 扩展欧几里得
    cogs333 荒岛野人 扩展欧几里得
    bzoj1123 BLO tarjan求点双连通分量
    poj3352 road construction tarjan求双连通分量
    cogs1804 联合权值 dp
    cogs2478 简单的最近公共祖先 树形dp
    cogs1493 递推关系 矩阵
    cogs2557 天天爱跑步 LCA
    hdu4738 Caocao's Bridge Tarjan求割边
  • 原文地址:https://www.cnblogs.com/coprince/p/8031407.html
Copyright © 2011-2022 走看看