CopyOnWriteArrayList遍历时不会出错,因为迭代器持有创建时的数组快照,写操作通过复制新数组并原子更新引用实现,读写互不干扰;推荐用增强for或iterator遍历,避免混合size/get导致不一致视图。

CopyOnWriteArrayList 适合读多写少场景,核心在于“读不加锁、写时复制”,遍历时完全不会抛 ConcurrentModificationException,也不阻塞读线程。
为什么遍历不会出错?
每次写操作(add、remove、set)都会创建新数组并原子更新内部引用;而迭代器持有的是快照数组(构造时的副本),所以遍历过程不受后续写操作影响。
这意味着:即使其他线程正在修改列表,你的 for-each 或 iterator 遍历仍能安全完成,看到的是某次写操作完成前的稳定状态。
正确遍历写法(避免常见坑)
直接使用增强 for 循环或显式 iterator 即可,无需额外同步:
- ✅ 推荐:
for (String s : list) { ... }—— 底层调用的是快照迭代器 - ✅ 安全:
Iterator<string> it = list.iterator(); while(it.hasNext()) {...}</string> - ❌ 错误:
for (int i = 0; i —— 虽然不会抛异常,但可能读到旧长度 + 新元素混合的不一致视图(size 和 get 看到的数组版本可能不同)
写操作的代价与适用边界
每次 add/remove 都要复制整个数组,时间复杂度 O(n),内存开销翻倍。因此只在满足以下条件时才用:
- 列表大小适中(几百到几千元素较稳妥)
- 写操作极少(如配置项动态刷新、监听器注册/注销)
- 读操作高频且要求低延迟、无锁、强一致性(非实时最新,但绝对不崩溃)
若写频次升高或列表很大,应考虑 ConcurrentHashMap 模拟、分段锁或读写锁等替代方案。
典型使用示例(监听器管理)
这是 CopyOnWriteArrayList 最经典的场景:
private final CopyOnWriteArrayList<EventListener> listeners = new CopyOnWriteArrayList<>();
public void fireEvent(Event e) {
// 遍历过程完全不担心 listener 被增删
for (EventListener l : listeners) {
l.onEvent(e);
}
}
public void addListener(EventListener l) {
listeners.add(l); // 写操作复制数组,不影响正在遍历的线程
}
多个线程可同时触发事件,新增监听器也不会干扰正在进行的回调。

















