• 多线程14-遍历集合时删除元素问题分析


    1. 问题

           创建一个User类:

    package cn.itcast.heima2;
    public class User implements Cloneable{
        private String name;
        private int age;
        
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public boolean equals(Object obj) {
            if(this == obj) {
                return true;
            }
            if(!(obj instanceof User)) {
                return false;    
            }
            User user = (User)obj;
            //if(this.name==user.name && this.age==user.age)
            if(this.name.equals(user.name) 
                && this.age==user.age) {
                return true;
            }
            else {
                return false;
            }
        }
        public int hashCode() {
            return name.hashCode() + age;
        }
        
        public String toString() {
            return "{name:'" + name + "',age:" + age + "}";
        }
        public Object clone()  {
            Object object = null;
            try {
                object = super.clone();
            } catch (CloneNotSupportedException e) {}
            return object;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public String getName() {
            return name;
        }
    } 

    执行下面的代码 :

    package cn.itcast.heima2;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.concurrent.CopyOnWriteArrayList;
    public class CollectionModifyExceptionTest {
        public static void main(String[] args) {  
            Collection<User> users  = new ArrayList<User>() ;
            users.add(new User("张三",28));     
            users.add(new User("李四",25));            
            users.add(new User("王五",31));      
            Iterator<User> itrUsers = users.iterator();
            
            while(itrUsers.hasNext()){
                System.out.println("aaaa");
                User user = (User)itrUsers.next(); 
                if("张三".equals(user.getName())){
                    users.remove(user); 
                    //itrUsers.remove(); 
                } else {
                    System.out.println(user);                
                }
            }
        }
    }     

    在遍历集合的时候如果查找到“张三” 则将张三的信息给删除了  代码初一看没有问题 ,但是一执行结果如下:

    aaaa
    aaaa
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
        at java.util.ArrayList$Itr.next(Unknown Source)
        at cn.itcast.heima2.CollectionModifyExceptionTest.main(CollectionModifyExceptionTest.java:17)

    出现了异常  这是为什么呢?

     2. 分析问题: 

         要想得到这个答案 需要去查看代码的执行过程 

        首先看

    Iterator<User> itrUsers = users.iterator();

    中的 users.iterator()调用的是ArrayList中的iterator方法 ,其源码为:

    public Iterator<E> iterator() {
            return new Itr();
        }

    返回的是 new Itr() ;其中Itr是ArrayList中的一个内部类 代码如下:

      private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @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];
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

            那么上面对iterator的遍历操作都是通过Itr中实现的

     程序中的itrUsers.hasNext() 调用的为Itr中的hasNext()方法

     public boolean hasNext() {
                return cursor != size;
            }

      其中的size 表示的是users集合的长度:size = 3  ; cursor 是int类型 默认值为0   那么第一次执行hasNext 的时候显然 cursor != size  返回true 

    然后看itrUsers.next()这段代码 执行的为: 

    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 为父类AbstractList中定义的protetct变量  初始为0    ; int expectedModCount = modCount;   开始两者是一致的  , 然后cursor = i + 1 ; 即cursor=1 ;

    接下来代码可以执行到:

        if("张三".equals(user.getName())){

     这里,接下来运行的是:

    users.remove(user); 

     remove的源码为: 

     public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // Let gc do its work
    
            return oldValue;
        }

    这段代码中要注意的有两个地方 ,其中1个为   modCount++;  即modCount = 1 ;  还有一个是     elementData[--size] = null;    这行代码删除了数组中的一个元素 同时也对size进行了减1操作

    即此时 size = 2 ;

          经过上述代码以后 张三的信息顺利的从集合中删除了, 接下来需要看是第二次循环:  

    还是 hasNext() 方法  由于cursor = 1  size = 2 ; 那么hasNext()返回true  成功的进入循环 

         那么开始执行itrUsers.next() ,需要调用checkForComodification() 方法 ,但是此时 expectedModCount  = 0  , modCount = 1 ;  程序代码会抛出异常

      throw new ConcurrentModificationException();

       程序代码到此结束。结果为删除张三抛出异常。

         

      3. 探索

           如果把代码修改为删除李四,效果是怎么样呢? 结果如下:

    aaaa
    {name:'张三',age:28}
    aaaa

     代码成功执行,没有任何异常 

          还是按照上面的思路来分析这个问题  

          size = 3    第一次hasNext  返回true,  执行next() 得到 cursor  = 1 ,第一次成功循环结束 

    开始第二次循环 

          size = 3  第二次hasNext  返回true  ,执行next() 得到cursor  = 2 ,发现是要删除李四了  执行remove 方法  此时  size = 2  , modCount  = 1  ,成功删除了李四的信息  

    开始第三次循环 

          size = 2  第三次hasNext  cursor = 2  和size相等  此时 hasNext 返回了false  没有进入循环 ,代码执行结束 ,导致能成功删除李四 没有任何异常.

    要想避免上述的问题  即在集合遍历的时候能对集合进行数据的增删操作  需要用到CopyOnWriteArrayList ,将程序修改如下: 

    package cn.itcast.heima2;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.concurrent.CopyOnWriteArrayList;
    public class CollectionModifyExceptionTest {
        public static void main(String[] args) { 
            Collection<User> users  = new CopyOnWriteArrayList<User>();
            users.add(new User("张三",28));     
            users.add(new User("李四",25));            
            users.add(new User("王五",31));      
            Iterator<User> itrUsers = users.iterator();
            
            while(itrUsers.hasNext()){
                System.out.println("aaaa");
                User user = (User)itrUsers.next(); 
                if("李四".equals(user.getName())){
                    users.remove(user); 
                    //itrUsers.remove(); 
                } else {
                    System.out.println(user);                
                }
            }
        }
    }     

     使用CopyOnWriteArrayList能成功的原因是其中 user.iterator()返回的不再是Itr了 ,而是CopyOnWriteArrayList中的COWIterator内部类: 

        public Iterator<E> iterator() {
            return new COWIterator<E>(getArray(), 0);
        }

    其中COWIterator代码可自行研究.

  • 相关阅读:
    深度优先搜索
    哈希算法
    双指针问题
    基本概念
    Ionic JPush极光推送二
    一条sql获取每个类别最新的一条记录
    Ionic App 更新插件cordova-plugin-app-version
    Ionic跳转到外网地址
    Ionic cordova-plugin-splashscreen
    Web API 上传下载文件
  • 原文地址:https://www.cnblogs.com/liaokailin/p/3799058.html
Copyright © 2020-2023  润新知