一种较为隐蔽ConcurrentModificationException情形

ConcurrentModificationException是遍历过程中修改list而抛出的错误。就像前面分析的,单线程时这种错误主要是因为使用forEach造成:遍历一个拷贝,修改原始list,造成的。简单的大家都知道,这里要说一种比较隐蔽的出错方式:

 1 public class ExceptionTest{
 2 
 3     private List<String> list = new ArrayList<>();
 4 
 5     public ExceptionTest(){
 6         for(int i=0;i<9;i++){
 7             list.add("string"+i);
 8         }
 9     }
10     
11     public void throwExceptions(){
12         List<List<String>>  listList = new ArrayList<>();
13         listList.add(list.subList(0,3));
14         listList.add(list.subList(3,6));
15         listList.add(list.subList(6,8));
16         Iterator<List<String>> it1 = listList.iterator();
17         while(it1.hasNext()){
18             List<String> l = it1.next();
19             Iterator<String> it = l.iterator();
20             while(it.hasNext()){
21                 String str = it.next();
22                 if(str.endsWith(("5"))){
23                     it.remove();
24                 }
25             }
26         }
27     }
28  }

运行上面的代码还是会出错,虽然两层循环都用来Iterator。如果不仔细看很难发现这其中的原因。当你调试你会发现,删除以5结尾的字符串的时候,listList中的第一个子list会报错,而不是正在做修改的第二个子list。所以这个错误的来源不是两个遍历的本事,而是listList的初始化方式,可以看到13到16行,每个子列表使用的是list的subList方法。进一步查看subList代码可以发现。这个方法其实并未新建list而是返回了原理list的一个视图(其实索引):

SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }

所以在修改第二个子list的时候回引起父列表(list)状态发生改变,虽然两层遍历看起来是正常的,但是他们却是在遍历和修改同一个list,因此会抛出ConcurrentModificationException异常。

总结:有时候的异常并不是因为当前的步骤引起的,原因有可能来自于之前的某一步骤。

posted @ 2016-03-13 20:36  zziawan  阅读(221)  评论(0编辑  收藏  举报