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

原文地址:https://www.cnblogs.com/liaokailin/p/3799058.html