ConcurrentModificationException 在单线程遍历时调用集合结构性修改方法(如list.remove())即触发,因迭代器的expectedModCount与集合modCount不匹配;多线程下应选用CopyOnWriteArrayList或ConcurrentHashMap等fail-safe容器。
ConcurrentModificationException 是怎么被触发的
它不是“并发”专属异常,而是集合结构被意外修改时的即时警报。核心就一句话:
和
对不上了。前者是集合自己记的“改过几次”,后者是迭代器创建时抄下来的快照。只要遍历中途有人调了
、
、
这类结构性方法(哪怕单线程),下次调
就会进
,然后直接抛
。
for-each 循环本质就是
+
,所以一样中招
不触发,因为它压根没
方法,也不维护
这个检测是“尽力而为”——JVM 不保证 100% 捕获所有并发修改,所以不能靠它做同步逻辑
单线程里为什么也会 fail-fast
很多人以为只有多线程才出问题,其实最常见场景就在自己写的 for-each 里删元素:
原因很简单:for-each 底层用的是迭代器,但你删元素用的是
,它只更新了
,没告诉迭代器——等下一轮
调用时,校验失败,立刻中断。
正确做法是用迭代器自己的
方法,它会同步更新
如果要删多个,别边遍历边删;先收集待删项,再用
用普通 for 循环从后往前删(
)能绕过异常,但逻辑易错,不推荐作为首选方案
多线程下怎么安全地遍历并修改 ArrayList
别硬扛
+ 手动加锁——性能差、易死锁、还容易漏锁点。真要并发读写,换容器比修逻辑更靠谱:
Eclipse导入Android或其他的JAVA项目的正确方法 WORD版
本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
下载
立即学习
“
Java免费学习笔记(深入)
”;
读多写少:用
,写操作复制数组,遍历永远看到快照,不抛异常,但内存开销大、写延迟高
读写均衡:用
替代
,它分段锁 + CAS,无
,天然不 fail-fast
必须用原生
?那就用
,但注意:迭代仍需手动同步整个块,否则还是可能出问题
fail-fast 和 fail-safe 的关键区别在哪
根本不在“快不快”,而在“看谁的数据”:
fail-fast(如
):盯着原始集合的
,改了就停,宁可中断也不读脏数据
fail-safe(如
或
):遍历的是快照或副本,原集合怎么改都影响不到当前迭代,但你也看不到那些新改的内容
没有银弹:fail-safe 避开了异常,却带来数据可见性问题;fail-fast 暴露了问题,但要求你主动处理结构变更逻辑
最容易被忽略的一点:很多开发者以为换成
就万事大吉,但它对
迭代器是 fail-safe,对
却仍是弱一致性——删掉的 entry 可能还在迭代中出现一次,这不是 bug,是设计使然。
modCountexpectedModCountaddremoveclearnext()checkForComodification()ConcurrentModificationExceptioniterator()while(hasNext())EnumerationremoveexpectedModCountList list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if ("b".equals(s)) {
list.remove(s); // ⚠️ 这里就炸了
}
} list.remove()modCountnext()remove()expectedModCountremoveAll()i--ArrayListCopyOnWriteArrayListConcurrentHashMapHashMapmodCountArrayListCollections.synchronizedList()ArrayList.iterator()modCountConcurrentHashMap.keySet().iterator()CopyOnWriteArrayList.iterator()ConcurrentHashMapkeySet()entrySet().iterator()