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