copyonwritearraylist 的 for-each 遍历不抛 concurrentmodificationexception,因其迭代器基于创建时刻的不可变数组快照,写操作复制新数组而不影响遍历;遍历见旧数据,写操作开销大,不支持遍历时修改。

CopyOnWriteArrayList 的 for-each 遍历本身不直接“实现”读写分离,但它天然适配读多写少、需遍历时避免并发修改异常的场景——其底层通过写时复制(Copy-On-Write)机制,让迭代器基于快照工作,从而在遍历过程中允许安全写入,这正是读写分离思想的一种轻量级落地。
for-each 遍历为何不会抛 ConcurrentModificationException
CopyOnWriteArrayList 的 for-each 本质调用的是 iterator() 方法,返回的迭代器持有创建时刻数组的**不可变快照引用**。即使其他线程此时调用 add/remove/set,原数组副本不受影响,遍历继续完成,不会触发 fail-fast 检查。
这意味着:你可以在一个线程中安全 foreach 遍历,同时另一个线程执行写操作,互不阻塞、无需额外同步。
典型读写分离流程控制示例
常见于事件通知、监听器广播、配置监听等场景,例如向多个监听器推送状态更新:
- 读侧(遍历):主线程或处理线程 foreach 通知所有监听器(只读访问)
- 写侧(变更):其他线程动态注册/注销监听器(写操作),不影响正在执行的通知过程
- 无显式锁、无手动加读写锁、无 CopyOnWriteArraySet 的额外去重开销
使用时的关键注意事项
虽然稳定,但需清醒认知其行为边界:
- 遍历看到的是“旧数据”:写操作生效后,后续新迭代器才能看到;当前 foreach 永远看不到本次写入的内容
- 写操作开销较高:每次 add/remove 都触发数组复制,频繁写会显著影响性能和 GC 压力
- 不支持在遍历中调用 remove() 或 set():迭代器的 remove() 是空实现,set() 抛 UnsupportedOperationException —— 这是设计使然,不是 bug
- 若需“边遍历边筛选移除”,应改用普通 ArrayList + 显式索引循环,或收集待删元素再批量清理
简单可靠的操作模式
推荐采用“只读遍历 + 异步写入”的组合:
- 声明为 final CopyOnWriteArrayList
listeners = new CopyOnWriteArrayList(); - 通知逻辑:for (Listener l : listeners) { l.onUpdate(data); }
- 注册监听:listeners.add(new MyListener());
- 注销监听:listeners.remove(listener);(安全,不影响正在进行的遍历)










