我门系统中大部分在是在使用Collection上进行迭代时抛出异常,但是我们系统都是使用的单线程,为什么会发生该异常了
我们参考下面错误代码,一个简单的需求,用户获取一个集合数据,需要讲不满足用户需求的数据删除掉
代码:
//从业务方法中查询数据
List<String> list = service.queryResult();
for (String value : list)
{
//bbb数据不是用户需要的数据,我们需要移除
if (value.equals("aaa"))
{
//将不满足用户数据删除
list.remove(value);
}
}
运行结果:
Exception in thread"main" java.util.ConcurrentModificationException
atjava.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.demo.ConcurrentExceptionDemo.main(ConcurrentExceptionDemo.java:24)
我们可以发现上面的代码并没有什么并发问题,到底是什么原因发生该异常,我们可以再次查看API文档
引用文档上面的话:“此异常不会始终指出对象已经由不同线程并发修改。如果单线程发出违反对象协定的方法调用序列,则该对象可能抛出此异常”现在我们知道了单线程也是会抛出该异常的,由于我们违反了对象的协定的方法调用序列,但是我们是在哪里违反了协定了?我们再次查看API文档上面发现: “行该操作的迭代器称为快速失败 迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险”,这句话什么意思了?看了很久没有搞明白,所以直接查看源代码发现:、
finalvoid checkForComodification(){
if (modCount != expectedModCount)
thrownew ConcurrentModificationException();
}
}
我们使用的迭代器在调用next()调用了改方法checkForComodification(),当每次检查modCount != expectedModCount就会抛出该异常,
下面我们来再次分析我们的代码
modCount是集合每次在调用remove和add方法时都会记录修改次数modCount++,但是我们迭代产生后expectedModCount就是一个固定值了,所以你只要在迭代时修改了集合就会造成modCount != expectedModCount,所以就抛出了ConcurrentModificationException
现在我们应该已经搞清楚了改异常产生的原因了,但是我们还是要实现上面的需求,不满足要求还是要删除,我们代码该怎么实现了
方案一:通过调用迭代器的remove方法来实现删除
Iterator<String> ite = list.iterator();
while(ite.hasNext())
{
//aaa数据不是用户需要的数据,我们需要移除
if (ite.next().equals("aaa"))
{
list.remove("aaa");
}
}
我们来看看为什么调用迭代器的remove方法不会发生该异常了,下面是该迭代器的remove方法源码
publicvoid remove() {
if (lastRet == -1)
thrownew IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
thrownew ConcurrentModificationException();
}
}
我们发现上面加粗标红的字体就应该明白原因了吧。
方案二:首先获取需要删除的元素,然后通过迭代要删除的元素来删除
List<String> delResults= new ArrayList<String>();
for (String value : list)
{
//bbb数据不是用户需要的数据,我们需要移除
if (value.equals("bbb"))
{
delResults.add(value);
}
}
//然后迭代要删除的集合来删除
for (String delValue :delResults)
{
list.remove(delValue);
}
这样也是没有问题的,但是我们发现方案二好像比方法一多做了线性循环。
集合抛出ConcurrentModificationException进阶
我们下面来看一下下面代码
package com.demo;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.SynchronousQueue;
public class HiddenIterator
{
privateSet<Integer> set = new HashSet<Integer>();
public voidadd(Integer i)
{
set.add(i);
}
public voidremove(Integer i)
{
set.remove(i);
}
public voidaddTenThings()
{
Random rand = new Random();
for (int i=0;i<10;i++)
{
add(rand.nextInt());
System.out.println("DEBUGadd ten elements to set : " + set);
}
}
public staticvoid main(String []args)
{
finalHiddenIterator hi = new HiddenIterator();
newThread(){
publicvoid run() {
hi.addTenThings();
};
}.start();
hi.addTenThings();
}
}
我们来看看改代码,暂时不要管上面标红的行,就是在添加了一个元素打印集合中的值,我们看看程序的原意就是通过两个线程来向集合中添加元素,运行该代码发现下面结果
Exception in thread "Thread-0" java.util.ConcurrentModificationException
atjava.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
atjava.util.AbstractCollection.toString(AbstractCollection.java:421)
at java.lang.String.valueOf(String.java:2826)
atjava.lang.StringBuilder.append(StringBuilder.java:115)
atcom.demo.HiddenIterator.addTenThings(HiddenIterator.java:28)
at com.demo.HiddenIterator$1.run(HiddenIterator.java:37)
我们上面已经知道集合是在做迭代集合的时候才会发生该异常,我们可以看集合的add()方法里也完全没有抛出该异常的代码。这就百思不得其解了,然后一句一句的代码查看,原来发现问题原因处在了我们上面标红的代码行。我们其实只要注释掉打印语句就不会发生该异常了
原来我们打印集合时,会自动调用toString()方法,我们现在查看toString()方法源码
public String toString() {
Iterator<E> i =iterator();
if (! i.hasNext())
return"[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = i.next();
sb.append(e == this ? "(this Collection)" : e);
if (! i.hasNext())
return sb.append(']').toString();
sb.append(", ");
}
原来在调用集合 toString 方法时做了集合的迭代操作,所以导致了改异常的产生,因此我们在使用集合时要注意集合的一些隐式迭代操作,整理了一下集合的 hashCode,equals,toString,containsAll,removeAll,retainAll 方法都做了隐式迭代,所以我们在并发环境下将集合作为另一个容器的元素和一个容易的 key 时要注意,以及把容器作为参数的构造函数时都会对容器进行迭代,因此在这些操作下都可能会引起 ConcurrentModificationException