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

多线程14-遍历集合时删除元素问题分析,布布扣,bubuko.com

时间: 2024-10-25 14:19:51

多线程14-遍历集合时删除元素问题分析的相关文章

Java 遍历Map时 删除元素

1 package net.nie.test; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.Map; 6 7 public class HashMapTest { 8 private static Map<Integer, String> map=new HashMap<Integer,String>(); 9 10 /** 1.HashMap 类映射不保证顺序:某些映射

Python遍历列表时删除元素

无论是使用for还是while,当在从前往后遍历的同时删除列表中的元素时,都会发生些问题. 要从lst = [1,4,0,1,5,0,3,5]中删除所有0元素,有两个比较好的方法: 1 使用filter: lst = filter(lambda x : x != 0, lst) 2 使用列表解析: lst = [x for x in lst if x != 0] 这两种方式既简洁,可读性又好 ref: https://segmentfault.com/a/1190000007214571 原文地

java 集合遍历时删除元素

本文探讨集合在遍历时删除其中元素的一些注意事项,代码如下 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import java.util.ArrayList; import java.util.Iterator; import java

STL容器遍历时删除元素

STL容器遍历时在循环体内删除元素最容易出错了,根本原因都是因为迭代器有效性问题,在此记下通用删除方法,该方法适用于所有容器: 1 std::vector<int> myvec; 2 3 std::vector<int>::iterator it = myvec.begin(); 4 while( it != myvec.end()) 5 { 6 it = myvec.erase(it); 7 } 容器list有个比较另类的删除方法,如下代码所示: std::list<int

python list遍历时删除元素

python list遍历时候删除还真需要注意下,今天帮同学处理数据,竟然傻逼了. 需求: 除了第一列,给每列加一个序号如:"1:0","2:0","3:20100307",然后删除冒号后为0的数据. 推荐做法: arrays = [ ['5001', '0', '0', '20100307', '20150109', '2', '3', '75', '0', '0', '114', '13', '2', '0', '0'], ['10001',

Java动态 遍历List 时删除List特征元素 异常问题 及解决方式总结

首先,这是一个极其简单的问题,大牛可忽略,新手可能会遇到,Java中遍历某个List 时删除该List元素 会抛出异常. 这一个简单的问题再高手严重不值一提,但新手可能会比较困惑,用哪种方式可以安全有效的实现遍历list删除某些特征元素? 方式: 方法1.普通for循环 遍历List,删List除自身 特征条目: 方法2.高级for循环 遍历List,删除List自身 特定条目: 方法3.引入参考List,for循环遍历删除原List 特定条目: 方法4.利用iterator 遍历删除List特

Java遍历集合的几种方法分析(实现原理、算法性能、适用场合)

概述 Java语言中,提供了一套数据集合框架,其中定义了一些诸如List.Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList. 除此之外,Java对于数据集合的遍历,也提供了几种不同的方式.开发人员必须要清楚的明白每一种遍历方式的特点.适用场合.以及在不同底层实现上的表现.下面就详细分析一下这一块内容. 数据元素是怎样在内存中存放的? 数据元素在内存中,主要有2种存储方式: 1.顺序存储,Random Access(Di

list遍历时删除元素

正如我们前面所知道. 在我们依靠遍历删除ArrayList时总是出现一些神奇的现象.比如 ArrayList<integer> arraylist = new ArrayList<integer>(); arraylist.add(1); arraylist.add(1); arraylist.add(2); arraylist.add(3); arraylist.add(1); 我们遍历并删除其中的1 for(int i = 0:i < arraylist.size();i

在遍历集合时是应该用for还是用foreach

为这条记录声明所匹配的 PostgreSQL 用户,值 all 表明它匹配 于所有用户.否则,它就是特定 PostgreSQL 用户的名字,多个用户名可以通过用逗号分隔的方法声明,在名字前面加上+代表匹配该用户组的所有用户.一个包含用户名的文件可以 通过在文件名前面前缀 @ 来声明,该文件必需和 pg_hba.conf 在同一个目录. 根据上面的js代码,如果页面宽度低于640px,那么页面中html的font-size也会按照(当前页面宽度/640)的比例变化.这样,页面中凡是应用了rem的作