博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java list 容器的ConcurrentModificationException
阅读量:4967 次
发布时间:2019-06-12

本文共 3775 字,大约阅读时间需要 12 分钟。

java中的很多容器在遍历的同时进行修改里面的元素都会ConcurrentModificationException,包括多线程情况和单线程的情况。多线程的情况就用说了,单线程出现这个异常一般是遍历(forEach)过程中的修改导致了list中的状态不一致,为了防止不一致带来不可预测的后果所以抛出异常。以ArrayList为例,每次操作都会进行内部状态检查,代码如下所示:

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

当这两个值不一样,就意味着之前的操作出现了异常。其中modCount是定义在AbstractList中,用来表示列表被修改的次数。expectedModCount则是定义在Iterator父类中,默认值等于modCount用来表示期望的修改次数。

但是有时候确实有遍历修改的需要,如遍历过程中删除不需要的条目。这种需求在单线程下也是可以满足的,可以使用以下两种方式:

public  void modifyIterator() {        Iterator
iterator = list.iterator(); while(iterator.hasNext()){ String str = iterator.next(); if(str.equals("")) iterator.remove(); } } public void modifyByIndex() { for(int i=0;i

通常使用Iterator,后者不常用,而且因为ArrayList在删除某个元素后元素会移动,因此会造成索引变化,对于遍历会有影响。但是如果使用forEach则会报错(也是新手容易犯的错误):

  public  void modifyForEach() {        for(String str:list){            list.remove(str);        }    }

如果你使用上面这样的代码必定会报错。为什么这种形式就会报错呢?首先看一下两个函数的机器码:

public void modifyIterator();    Code:       0: aload_0       1: getfield      #17                 // Field list:Ljava/util/List;       4: invokeinterface #34,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;       9: astore_1      10: goto          19      13: aload_1      14: invokeinterface #56,  1           // InterfaceMethod java/util/Iterator.remove:()V      19: aload_1      20: invokeinterface #49,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z      25: ifne          13      28: return}public void modifyForEach();    Code:       0: aload_0       1: getfield      #17                 // Field list:Ljava/util/List;       4: invokeinterface #34,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;       9: astore_2      10: goto          34      13: aload_2      14: invokeinterface #38,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;      19: checkcast     #44                 // class java/lang/String      22: astore_1      23: aload_0      24: getfield      #17                 // Field list:Ljava/util/List;      27: aload_1      28: invokeinterface #46,  2           // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z      33: pop      34: aload_2      35: invokeinterface #49,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z      40: ifne          13      43: return

可以看到forEach的遍历过程也使用了Iterator。但是为什么后者会报错呢?原因其实很简单,forEach在开始时候获得Iterator,删除过程中没有更新Iterator的状态,所以导致了最后的状态不一致。首先看一下Iterator的删除代码:

public boolean remove(Object o) {        if (o == null) {            for (int index = 0; index < size; index++)                if (elementData[index] == null) {                    fastRemove(index);                    return true;                }        } else {            for (int index = 0; index < size; index++)                if (o.equals(elementData[index])) {                    fastRemove(index);                    return true;                }        }        return false;    }    private void fastRemove(int index) {        modCount++;        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    }

fastRemove方法中修改了modCount,但是由于Iterator已经回去,它里面的expectedModCount却没有更新,因此导致了两者的不一致。

总结:多线程同时修改list或单线程使用forEach遍历修改list过程,很多list会报ConcurrentModificationException错误。这个错误是由于Iterator中的expectedModCount与list中modCount不一致导致的。因为forEach也是使用了Iterator,但是修改却使用了list的相关方法,Iterator不会随着更新。所以多线程情况下任何操作需要同步避免list中数据不一致,而单线程的遍历修改一定要使用Iterator。

 

转载于:https://www.cnblogs.com/zziawanblog/p/5269374.html

你可能感兴趣的文章
laravel5.2 移植到新服务器上除了“/”路由 ,其它路由对应的页面显示报404错误(Object not found!)———新装的LAMP没有加载Rewrite模块...
查看>>
潜罪犯
查看>>
编写高质量代码--改善python程序的建议(六)
查看>>
windows xp 中的administrator帐户不在用户登录内怎么解决?
查看>>
[spfa] Jzoj P4722 跳楼机
查看>>
代码审计入门后审计技巧
查看>>
Linux-Rsync服务器/客户端搭建实战
查看>>
接口和抽象类有什么区别
查看>>
简单通过百度api自动获取定位-前端实现
查看>>
180117 我的宠物识别判断语句
查看>>
JavaScript修炼之道pdf
查看>>
自己动手构造编译系统++编译、汇编与链接pdf
查看>>
JAVA 中文件读写函数BufferedReader 和 BufferedWriter 的使用
查看>>
Codeforces Round #206 (Div. 2)
查看>>
提升混合应用页面打开速度的新思路
查看>>
Mycat分表分库
查看>>
2019.7.11
查看>>
Php取扩展名
查看>>
模板的文件名和方法名一定要一致!!
查看>>
**p
查看>>