copyonwritearraylist的fail-safe机制指其迭代器基于创建时的快照副本遍历,不抛concurrentmodificationexception;适合读多写少场景,如事件监听器广播,但写操作开销大、不反映实时修改。

CopyOnWriteArrayList 的“安全失败”(fail-safe)机制,并不是指它能防止错误,而是指它的迭代器在遍历时不会抛出 ConcurrentModificationException —— 因为它不依赖原始集合的结构变化做校验,而是基于创建迭代器那一刻的**快照副本**进行遍历。这种设计天然适合“读多写少”的并发场景,比如监听器列表、配置项缓存、事件广播等。
理解 fail-safe 的本质:迭代器用的是快照,不是原列表
CopyOnWriteArrayList 在调用 iterator() 时,会将当前数组的一个不可变副本(snapshot)赋给迭代器。后续所有 next() 操作都只在这个副本上进行,与原列表是否被其他线程增删元素完全无关。
这意味着:
- 迭代过程中,其他线程对列表的
add()、remove()、set()操作,不会影响当前迭代器的行为,也不会触发异常; - 但当前迭代器也永远看不到这些新修改——它看到的始终是迭代开始那一刻的状态;
- 每次写操作都会触发数组复制(copy-on-write),所以写操作开销大,不适合高频修改场景。
典型实战场景:事件监听器的线程安全广播
这是 CopyOnWriteArrayList 最经典的应用。假设你有一个事件总线,多个监听器注册进来,主线程或定时任务触发事件,而监听器执行可能耗时甚至阻塞。此时既要保证遍历时不因监听器移除崩溃,又要避免加锁拖慢广播速度。
示例代码逻辑:
public class EventBus {
private final CopyOnWriteArrayList<eventlistener> listeners = new CopyOnWriteArrayList();
<pre class="brush:java;toolbar:false;">public void addListener(EventListener l) { listeners.add(l); }
public void removeListener(EventListener l) { listeners.remove(l); }
public void fireEvent(Event event) {
// 迭代器基于当前快照,即使有监听器在处理中自行注销,也不影响本次遍历
for (EventListener listener : listeners) {
try {
listener.onEvent(event);
} catch (Exception ignored) { /* 避免单个监听器异常中断整个广播 */ }
}
}
}
关键点:
- 监听器可在
onEvent()中安全调用removeListener(this),不会导致ConcurrentModificationException或迭代跳过/重复; - 新增监听器会在下一次
fireEvent()时生效,符合“最终一致性”预期; - 无需手动加锁或使用
synchronized,代码简洁且可读性强。
注意事项与避坑指南
CopyOnWriteArrayList 不是万能并发容器,用错场景反而引入隐蔽问题:
- 不适用于写频繁场景:每次 add/remove 都复制整个数组,10 万个元素写一次 ≈ 分配 10 万对象引用的新数组,GC 压力陡增;
-
迭代器不反映实时状态:若业务逻辑依赖“边遍历边感知最新数据”,比如实时统计在线用户并踢掉超时者,它就不合适(应选
ConcurrentHashMap+ 手动同步或ReentrantReadWriteLock); -
size() 和迭代结果可能不一致:调用
size()返回的是当前最新长度,但刚拿到的迭代器仍按旧快照走,二者数值可能不同; -
无法替代同步块做复合操作:如“检查是否存在再添加”,这不是原子操作,CopyOnWriteArrayList 不提供 CAS 类型的条件写入方法,需额外控制(如用
Set配合)。
替代方案对比:什么情况下该换别的工具?
当需求超出 CopyOnWriteArrayList 能力边界时,可考虑:
-
读写均衡 or 写稍多 → 用
ConcurrentLinkedQueue(无界非阻塞队列)或ConcurrentSkipListSet(有序去重); -
需要强一致性遍历 + 高频写 → 用
ReentrantReadWriteLock包裹普通ArrayList,读锁允许多个线程并发读,写锁独占; -
键值查找为主 + 并发修改 → 直接上
ConcurrentHashMap,它支持安全迭代、分段锁、CAS 更新,适用性更广。
不复杂但容易忽略:选择前先问自己——这个集合,是“几乎只读+偶尔增删”,还是“每秒改几十次”?答案决定了要不要从 CopyOnWriteArrayList 切出去。










