记录一次 Collection 集合关于 ConcurrentModificationException 的“趣事”
Iterator和并发修改异常
对Java集合类源码稍有了解的人或许都知道 Iterator和并发修改异常
的问题,即当使用 Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变(如通过调用集合的 remove 方法进行删除),
只能通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素,否则将会引发 java.util.ConcurrentModificationException
异常。
这是由于 Iterator 迭代器采用的是 快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发 `ConcurrentModificationException` 异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
/**
其实现原理大致如下:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中不断检查集合中的一个 modCount 变量。
集合在被遍历期间如果 结构 发生变化,就会改变modCount的值。
每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为迭代器的expectedmodCount值,是的话就返回遍历结果;
否则抛出ConcurrentModificationException异常,终止遍历
注意:这里异常的抛出条件是检测到 `modCount!=expectedmodCount` 这个条件。
*/
示例代码:
public class IteratorDemo {
public static void main(String[] args) throws Exception{
final ArrayList<String> list = new ArrayList<>();
// final Vector<String> list = new Vector<>();
// final LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list);
final Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
final String next = iterator.next();
System.out.println(next);
if ("aaa".equals(next)) list.remove(next);
}
System.out.println(list);
}
}
结果显然会出错:
接下来才是这次的重点,请看如下代码:
public class IteratorDemo {
public static void main(String[] args) throws Exception{
final ArrayList<String> list = new ArrayList<>();
// final Vector<String> list = new Vector<>();
// final LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
//list.add("ccc"); // 唯一的区别!!!
System.out.println(list);
final Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
final String next = iterator.next();
System.out.println(next);
if ("aaa".equals(next)) list.remove(next);
}
System.out.println(list);
}
}
运行结果如何呢?既然在此提出来了,那肯定不会像我们的第一直觉那样抛出异常,相反,程序‘异常’顺利的运行下去了......
(
)
或许不会有人像我这么多事,但我很好奇,这是为什么呢?而且你有兴趣的话多试试添加不同的元素个数,结果ArrayList和Vector都是只有两个元素的时候没有抛异常!!!
这不得不说有些让人费解,然而这种情形,我们也只能借助源码探究原因了(发现这个问题的当晚,状态实在不佳,请教了同事才弄明白,在此默默感谢大佬 Thanks♪(・ω・)ノ3 )
到这里就不得不再看看集合类中关于 Iterator 的实现原理了,先看看 ArrayList 的部分实现:
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
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;
Itr() {}
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];
}
// ......
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
iterator() 方法会返回一个内部类,重点在于该类的 hasNext() 方法的实现,
上述示例代码中,当循环进行到第二次时,cursor 值 为 1, 而 size 也因为删除了一个元素 值也恰巧为 1 (emmm....)
hasNext() 将返回 false ......
结果显而易见了,根本运行不到检查 modCount 的 next() 方法中,这里会立即结束循环,结果就是控制台输出的样子了!!!
不知道看到这里的你什么心情,我反正已经麻了,这并不是什么有用的发现,只是对其不一致的运行结果感到有些困惑而已,强迫症没得救了...
这也再次证实了不应该在使用Iterator遍历集合时,使用非Iterator 提供的添加删除等改变结构的方法,否则不仅可能会抛出异常,还可能像该例中那样导致遍历提前结束
PS:有兴趣的你获取可以试试 Linkedlist ,其 迭代实现与 ArrayList 和 Vector 略有不同,但在次情形下最大的区别仅在 hasNext()
方法中:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
// 看看这里
public boolean hasNext() {
return nextIndex < size;
}
// ......
}
想想看 Linkedlist 会不会发生跟 ArrayList 和 Vector 类似的情况呢? 例如只有一个元素时 emmm......
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!