copyonwritearraylist 迭代器不抛 concurrentmodificationexception 是因为它基于创建时的数组快照,modcount 不变;remove 操作不影响当前迭代,适用读多写少场景,但不适用于实时性要求高或写频繁的场景。

为什么 CopyOnWriteArrayList 的迭代器不会抛 ConcurrentModificationException
因为它的迭代器基于创建时的数组快照,所有修改操作(add、remove)都作用在新副本上,原快照不受影响。这不是“修复了迭代器”,而是根本绕开了同步检查——modCount 在迭代期间从不被修改。
常见错误现象:用普通 ArrayList 在多线程中边遍历边 remove,直接触发 ConcurrentModificationException;换成 CopyOnWriteArrayList 后异常消失,但删掉的元素下一轮遍历还在,误以为“没删成功”。
- 适用场景:读多写少,且能接受迭代器看到的是“旧数据”
- 不适用场景:实时性要求高(比如监控列表状态变化)、写操作频繁(每次写都复制整个数组,GC 压力大)
-
CopyOnWriteArrayList的iterator()返回的是COWIterator,它持有的是构造时的数组引用,后续任何写操作都不会改变它
CopyOnWriteArrayList 的 remove 方法为什么不能在迭代中安全调用
它能调用,也不抛异常,但效果不符合直觉:调用 iterator.remove() 是非法的,会抛 UnsupportedOperationException;而调用 list.remove() 虽然成功,但不影响当前迭代器——因为迭代器用的是老副本,删的是新副本。
使用场景举例:后台线程定时清理过期任务,同时有多个线程在遍历任务列表做执行判断。此时用 list.removeIf(...) 比在 for-each 里调 list.remove() 更清晰,也避免误用迭代器 remove。
立即学习“Java免费学习笔记(深入)”;
-
iterator.remove()→ 直接抛UnsupportedOperationException,别试 -
list.remove(Object)或list.removeIf(Predicate)→ 安全,但本次迭代看不到效果 - 如果需要“边遍历边过滤”,改用
list.stream().filter(...).collect(Collectors.toList()),别在迭代器里动原集合
替代方案对比:ConcurrentHashMap 和 CopyOnWriteArrayList 选哪个
不是“哪个更好”,是“谁更贴合你的访问模式”。CopyOnWriteArrayList 适合按索引或顺序遍历为主的场景;ConcurrentHashMap 适合靠 key 查找、并发读写键值对的场景。两者都解决迭代器失效问题,但机制完全不同。
性能影响明显:10 万元素的 CopyOnWriteArrayList 每次 add 都要复制 10 万引用,而 ConcurrentHashMap 的写操作只锁对应 segment 或 node。
- 你要遍历全部元素做计算?→
CopyOnWriteArrayList可能更简单 - 你要频繁
get(key)或computeIfAbsent?→ 必须用ConcurrentHashMap - 你既需要遍历又需要随机查?→ 考虑拆成两个结构,或用
ConcurrentSkipListMap(支持有序遍历+并发访问)
容易被忽略的内存与可见性细节
CopyOnWriteArrayList 的“写后读到新值”不是即时的。新线程拿到的 list 引用,可能仍指向旧数组,直到下次 get 或 size 触发 volatile 读;而迭代器一旦创建,就彻底与后续写隔离。
最常踩的坑:在循环里反复调 list.size() 判断是否为空,却在别处清空了 list——由于 size 是 volatile 读,能看到最新值;但如果你用 for (int i = 0; i ,中间写操作可能导致某些元素被跳过(因为 size 缩小了,但数组副本未更新)。
- 迭代器生命周期内看到的数据版本是固定的,哪怕外部已修改十次
-
get(index)是 volatile 读,能看到最新数组,但不保证该 index 上的元素是“最新写入”的(可能仍是旧副本里的) - 不要依赖
size()控制遍历范围,要用迭代器自身逻辑(如hasNext())










