CopyOnWriteArrayList通过写时复制避免ConcurrentModificationException,迭代器基于创建时数组快照工作,读写并行但读不可见最新写;遍历时安全删除须用Iterator.remove(),禁用list.remove()和Iterator.add()/set();适用于读多写少、弱一致性可接受场景。

CopyOnWriteArrayList 通过“写时复制”机制天然避免 ConcurrentModificationException,核心在于:**迭代器不检查结构变化,而是直接基于创建时刻的数组快照工作**。它不是靠同步或锁来“阻止冲突”,而是让读与写在不同副本上并行——因此遍历时调用 add() 或 remove() 不会触发异常。
迭代器始终读取不可变快照
每次调用 iterator() 或进入 for-each 循环时,迭代器会保存当前内部数组的引用(即一个只读副本)。后续所有 next() 操作都只在这个副本上进行:
- 其他线程执行
add()、remove()时,会新建数组、复制数据、再原子更新内部volatile引用; - 已有迭代器仍指向旧数组,既看不到新修改,也不会因结构变动校验失败;
- 所以不会抛
ConcurrentModificationException,也不需要synchronized或显式锁。
遍历时安全删除的正确写法
必须区分“谁来删”——不能靠集合自身方法在增强 for 中删,而要使用迭代器自身的 remove():
- ✅ 正确:
Iterator.remove()—— CopyOnWriteArrayList 重写了该方法,内部加锁并更新数组,线程安全; - ❌ 错误:
list.remove()配合for-each—— 虽不抛异常,但删的是“下一个快照”,当前循环仍遍历原数组,逻辑失效; - ❌ 禁止:
Iterator.add()或Iterator.set()—— 全部抛UnsupportedOperationException。
写操作不干扰读,但读也看不到最新写
这是“弱一致性”的体现,也是避免异常的代价:
立即学习“Java免费学习笔记(深入)”;
- 遍历中途有线程
add("x"),本次循环一定看不到"x"; -
size()返回的是最新数组长度,但迭代器仍按旧长度走完; - 多个线程同时遍历,各自看到的可能是不同时间点的快照,没有全局统一视图。
适用边界:别在不适合的场景硬套
它解决的是“遍历时删/增不崩溃”,不是万能并发方案:
- 写频繁(如每秒数百次)→ 数组反复复制,CPU 和内存压力陡增;
- 元素大且多(如存 MB 级 byte[])→ 一次
add()就触发大量内存拷贝; - 需要强一致(如状态同步、计数器)→ 它只保证最终一致,不保证实时可见;
- 要用
ListIterator双向遍历并修改 → 它的listIterator()同样不支持add()和set()。


















