copyonwritearraylist通过读无锁、写时复制数组实现线程安全,迭代器基于创建时刻的数组快照,故遍历时不抛concurrentmodificationexception;适用于监听器列表、配置白名单、服务发现节点等读多写少场景。

CopyOnWriteArrayList 是 Java 并发包中专为“读多写少”场景设计的线程安全列表,它的核心思路是:**读操作完全无锁,写操作加锁并复制底层数组**。遍历安全的关键在于——迭代器持有的是快照,不会因其他线程修改而抛出 ConcurrentModificationException,也不受实时变更影响。
为什么遍历时不会报 ConcurrentModificationException?
因为 CopyOnWriteArrayList 的 iterator() 返回的是一个只读快照迭代器(COWIterator),它内部持有一个创建时刻数组的引用副本。后续所有 add/remove/set 操作都会先加锁、复制新数组、再更新 volatile 引用,但已存在的迭代器仍指向旧数组,所以遍历过程绝对安全,无需额外同步。
适合哪些典型读多写少场景?
- 监听器/观察者列表(如事件回调注册表,注册/注销极少,通知遍历极频繁)
- 配置项白名单、状态枚举集合等只在启动或运维时变更,运行时高频读取
- 缓存中的元数据索引(如服务发现节点列表,变更频率远低于查询频率)
使用时必须注意的关键细节
看似简单,但几个隐含行为容易引发问题:
- 写操作开销大:每次 add/remove 都要复制整个数组,元素越多、写越频繁,GC 压力和 CPU 开销越明显——别把它当普通 ArrayList 用
- 迭代器不反映实时修改:遍历时新增的元素对当前迭代器不可见,删除的元素仍会遍历到——这不是 bug,是设计使然
- size() 和 isEmpty() 是快照值:它们返回的是调用时刻的长度,不是严格实时的;但通常足够用于判断
- 不支持 null 元素:add(null) 会直接抛 NullPointerException
一个安全遍历的典型写法示例
不需要 synchronized,也不需要手动 try-catch CME:
// 安全遍历,无异常风险
for (String item : list) {
process(item); // 即使其他线程正在 add/remove,这里也稳定执行
}
// 如需在遍历时做条件过滤并移除,不能直接调用 remove()
// 正确做法:先收集待删项,遍历结束后统一 remove
List<string> toRemove = new ArrayList();
for (String item : list) {
if (shouldRemove(item)) {
toRemove.add(item);
}
}
list.removeAll(toRemove); // 这里才是线程安全的批量删除
</string>
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











