首页 > 代码库 > 多线程14-遍历集合时删除元素问题分析

多线程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);                            }        }    }}     

 

 

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

aaaaaaaaException 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代码可自行研究.