zoukankan      html  css  js  c++  java
  • java list 容器的ConcurrentModificationException

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

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

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

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

        public  void modifyIterator() {
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String str = iterator.next();
                if(str.equals(""))
                    iterator.remove();
            }
        }   
    
         public  void modifyByIndex() {
            for(int i=0;i<list.size();i++){
                    String str = list.get(i)
                    if(str.equals(""))
                    list.remove(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。

  • 相关阅读:
    写一个工具生成数据库实体类
    自己写一个java的mvc框架吧(三)
    自己写一个java的mvc框架吧(二)
    自己写一个java的mvc框架吧(一)
    手把手教你写一个java的orm(完)
    JavaEE系列之(二)commons-fileupload实现文件上传、下载
    JavaEE系列之(一)JSP基础知识详解
    Servlet---JavaWeb技术的核心基础,JavaWeb框架的基石(二)
    Servlet---JavaWeb技术的核心基础,JavaWeb框架的基石(一)
    cygwin简介及使用
  • 原文地址:https://www.cnblogs.com/zziawanblog/p/5269374.html
Copyright © 2011-2022 走看看