copyonwritearraylist 迭代不抛 concurrentmodificationexception 是因其迭代器采用快照机制:创建时复制数组引用,后续读操作均基于该不可变副本,与写操作完全隔离;这是明确的设计行为,非 bug。

CopyOnWriteArrayList 迭代时为什么不会抛 ConcurrentModificationException
因为它的迭代器是 fail-safe 的,不是基于共享的 modCount 做校验,而是直接持有一份创建时刻的数组快照。只要迭代器初始化完成,后续所有读操作都只在那个副本上进行,跟原列表的写操作完全隔离。
这和 ArrayList 的 Iterator 不同——后者每次 next() 都会检查 modCount 是否被外部修改过,一变就抛 ConcurrentModificationException。
-
CopyOnWriteArrayList.iterator()立即复制当前数组引用(注意:是引用拷贝,不是深拷贝元素),这个数组对象生命周期由迭代器持有 - 所有写操作(
add、remove、set)都会新建数组、复制旧数据、再更新内部array字段,老数组不受影响 - 所以迭代器永远看不到“中间状态”,也永远不会因为并发写而失败
迭代过程中看不到新添加的元素,这是 bug 还是设计使然
这是明确的设计行为,不是 bug。迭代器看到的是构造时的快照,之后的 add 或 remove 对它不可见。
比如你边遍历边调用 list.add(x),新元素只会出现在下一次 iterator() 创建后的迭代中。
- 适用场景:读多写少,且对“实时性”要求不高,比如监听器列表、配置项缓存
- 不适用场景:需要强一致视图的业务逻辑,例如银行账户余额同步校验
- 如果误以为能“边改边读”,容易漏处理刚加入的数据,这是最常踩的坑
为什么 add/remove 性能差,但 get 很快
get(int index) 是纯数组访问,O(1);而 add(E e) 每次都要 Arrays.copyOf 整个底层数组,时间复杂度 O(n),内存开销也随大小线性增长。
- 写操作会触发 full copy,10 万个元素就要拷贝 10 万次引用,GC 压力明显上升
- 多个写线程同时调用
add,会串行化在ReentrantLock上,锁竞争成为瓶颈 -
get完全无锁,甚至比ArrayList略快(少了 modCount 检查)
能不能用 for-each 遍历 CopyOnWriteArrayList
可以,但要注意 for-each 底层就是调用 iterator(),所以它同样拿到的是快照,行为和显式用 Iterator 一致。
下面这段代码不会抛异常,但也不会打印出循环中 add 的元素:
for (String s : list) {
System.out.println(s);
if ("a".equals(s)) list.add("new-item"); // 这个 new-item 不会被本次循环看到
}
如果你依赖“遍历时动态响应新增”,就得换方案:比如用 ConcurrentLinkedQueue + 显式消费循环,或者加业务层重试/轮询逻辑。
快照语义看着简单,但一旦写操作和迭代逻辑耦合,就很容易掉进“我以为它实时,其实它冻结”的陷阱里。










