zoukankan      html  css  js  c++  java
  • [转]Java在操作ArrayList、HashMap、TreeMap等容器类时,遇到了java.util.ConcurrentModificationException异常

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

    [java] view plain copy
     
    1. import java.util.ArrayList;  
    2. import java.util.Iterator;  
    3. import java.util.List;  
    4. import java.util.concurrent.CopyOnWriteArrayList;  
    5.   
    6. public class Test {  
    7.   
    8.     public static void main(String[] args){  
    9.         List<String> strList = new ArrayList<String>();  
    10.         strList.add("string1");  
    11.         strList.add("string2");  
    12.         strList.add("string3");  
    13.         strList.add("string4");  
    14.         strList.add("string5");  
    15.         strList.add("string6");  
    16.           
    17.         // 操作方式1:while(Iterator);报错  
    18.         Iterator<String> it = strList.iterator();  
    19.         while(it.hasNext()) {  
    20.             String s = it.next();  
    21.             if("string2".equals(s)) {  
    22.                 strList.remove(s);  
    23.             }  
    24.         }  
    25.           
    26.         // 解决方案1:使用Iterator的remove方法删除元素  
    27.         // 操作方式1:while(Iterator):不报错  
    28. //      Iterator<String> it = strList.iterator();  
    29. //      while(it.hasNext()) {  
    30. //          String s = it.next();  
    31. //          if("string2".equals(s)) {  
    32. //              it.remove();  
    33. //          }  
    34. //      }  
    35.           
    36.         // 操作方式2:foreach(Iterator);报错  
    37. //      for(String s : strList) {  
    38. //          if("string2".equals(s)) {  
    39. //              strList.remove(s);  
    40. //          }  
    41. //      }  
    42.           
    43.         // 解决方案2:不使用Iterator遍历,注意索引的一致性  
    44.         // 操作方式3:for(非Iterator);不报错;注意修改索引  
    45. //      for(int i=0; i<strList.size(); i++) {  
    46. //          String s = strList.get(i);  
    47. //          if("string2".equals(s)) {  
    48. //              strList.remove(s);  
    49. //              strList.remove(i);  
    50. //              i--;// 元素位置发生变化,修改i  
    51. //          }  
    52. //      }  
    53.           
    54.         // 解决方案3:新建一个临时列表,暂存要删除的元素,最后一起删除  
    55. //      List<String> templist = new ArrayList<String>();  
    56. //      for (String s : strList) {  
    57. //          if(s.equals("string2")) {  
    58. //              templist.add(s);  
    59. //          }  
    60. //      }  
    61. //      // 查看removeAll源码,其使用Iterator进行遍历  
    62. //      strList.removeAll(templist);  
    63.           
    64.         // 解决方案4:使用线程安全CopyOnWriteArrayList进行删除操作  
    65. //      List<String> strList = new CopyOnWriteArrayList<String>();  
    66. //      strList.add("string1");  
    67. //      strList.add("string2");  
    68. //      strList.add("string3");  
    69. //      strList.add("string4");  
    70. //      strList.add("string5");  
    71. //      strList.add("string6");  
    72. //      Iterator<String> it = strList.iterator();  
    73. //      while (it.hasNext()) {  
    74. //          String s = it.next();  
    75. //           if (s.equals("string2")) {  
    76. //               strList.remove(s);  
    77. //          }  
    78. //      }  
    79.           
    80.     }     
    81. }  

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

    [plain] view plain copy
     
    1. Exception in thread "main" java.util.ConcurrentModificationException  
    2.     at java.util.ArrayList$Itr.checkForComodification(Unknown Source)  
    3.     at java.util.ArrayList$Itr.next(Unknown Source)  
    4.     at concurrentModificationException.Test.main(Test.java:21)  


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

    [java] view plain copy
     
    1. @SuppressWarnings("unchecked")  
    2. public E next() {  
    3.     checkForComodification();  
    4.     int i = cursor;  
    5.     if (i >= size)  
    6.         throw new NoSuchElementException();  
    7.     Object[] elementData = ArrayList.this.elementData;  
    8.     if (i >= elementData.length)  
    9.         throw new ConcurrentModificationException();  
    10.     cursor = i + 1;  
    11.     return (E) elementData[lastRet = i];  
    12. }  


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

    [java] view plain copy
     
    1. final void checkForComodification() {  
    2.     if (modCount != expectedModCount)  
    3.         throw new ConcurrentModificationException();  
    4. }  


    即,比较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方法了,也就没有抛出异常,因此删掉倒数第二个元素时不会抛异常的异常。

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

  • 相关阅读:
    getElementsByTagName 与 $(élement)的区别
    php面向对象学习笔记
    使用php添加定时任务
    JS中数组Array的用法
    大陆居民身份证真伪校验
    安卓 日常问题 工作日志15
    安卓 日常问题 工作日志14
    安卓 日常问题 工作日志13
    安卓 日常问题 工作日志12
    安卓 日常问题 工作日志11
  • 原文地址:https://www.cnblogs.com/pengxupx/p/7490029.html
Copyright © 2011-2022 走看看