在用迭代器遍历集合时,当集合的结构被修改,会抛出 ConcurrentModificationException 异常。
java.util 包下的集合类都是快速失败的, 常见的的使用 fail-fast 方式遍历的容器有 HashMap 和 ArrayList 等。
1.单线程环境
集合在遍历的过程中,如果要对集合进行增删操作,没有调用迭代器的方法,而是用的集合自身的方法,则可能会产生 fail-fast (快速失败)
2.多线程环境下
当一个线程在遍历某个集合的过程中,另一个线程对集合的结构进行了修改,则可能产生 fail-fast (快速失败)
具体效果我们看如下代码:
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("c");
arrayList.add("d");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
arrayList.add("e"); ①
System.out.println("此时 arrayList 长度为" + arrayList.size());
} 执行后的效果如下图:
当迭代器在遍历时,不对 arrayList 进行修改,也就是删除①时。
当迭代器在遍历时,对 arrayList 进行修改,也就保留①处代码。就会抛出 ConcurrentModificationException 异常
modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedModCount 值,是的话就返回遍历;否则抛出异常,终止遍历。 采用安全失败的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历,所以对原集合的修改并不会被迭代器检测到
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会抛 ConcurrentModificationException 异常。
java.util.concurrent 包下面的所有的类都是安全失败的,如 ConcurrentHashMap , CopyOnWriteArrayList 等
具体效果我们看如下代码:
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
concurrentHashMap.put("a", 1);
concurrentHashMap.put("b", 2);
concurrentHashMap.put("c", 3);
Set set = concurrentHashMap.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
concurrentHashMap.put("d", 4); ②
}
System.out.println("结束");
} 执行后的效果如下图:
当迭代器在遍历时,对 concurrentHashMap 进行修改时,也就②。并未抛出任何异常
为什么在用迭代器遍历时,修改集合不会抛异常?
由于迭代时是对原集合的拷贝进行遍历,内部都是保存了该集合对象的一个快照副本,并且没有 modCount 等数值做检查。
fail-safe(安全失败)允许在遍历的过程中对容器中的数据进行修改,而fail-fast(快速失败)则不允许。