您的位置:首页 > 其它

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

2014-06-20 12:01 232 查看
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代码可自行研究.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: