CopyOnWriteArrayList适合读多写少场景,通过写时复制实现无锁读,但写操作开销大、实时性差;不适用于强一致性、迭代中修改或高频写场景,需依实际需求权衡替代方案。

CopyOnWriteArrayList 适合读多写少的并发场景
它本质是通过“写时复制”规避并发修改问题,每次写操作都新建数组并替换引用,因此写操作开销大、实时性差,但读操作完全无锁、零同步成本。适合那些读操作频次远高于写操作(比如 100:1 以上),且对写操作的实时性不敏感的场景。
典型例子包括:
- 事件监听器列表(Listener List),注册/注销极少,触发通知极频繁
- 配置项白名单、黑名单缓存(内容基本不变,只在运维时更新)
- 状态快照类集合(如监控系统中定期采集的活跃连接 ID 列表)
不能用于需要强一致性或迭代中修改的逻辑
因为 CopyOnWriteArrayList 的迭代器是弱一致性的:它基于创建时的数组快照,所以迭代过程中即使其他线程已修改集合,迭代器也看不到新元素,也不会抛 ConcurrentModificationException——这看似友好,实则容易掩盖逻辑错误。
常见误用包括:
- 在 for-each 循环中调用 remove() 或 add()(实际操作的是旧副本,当前迭代器不受影响,但业务意图往往被破坏)
- 依赖“写完立刻能被下一次读到”,比如用它做任务队列或状态协调器(size() 和实际读取结果可能不一致)
- 与 Stream.parallelStream() 混用——并行流可能跨多个快照,导致数据重复或遗漏
写操作性能差,且有内存放大风险
每次 add()、set()、remove() 都要复制整个底层数组,时间复杂度 O(n),内存占用瞬时翻倍。如果集合平均大小为 10 万元素,单次写操作会临时多占约 800KB(假设每个引用 8 字节),GC 压力明显。
需警惕的边界情况:
- 集合长期增长但极少清理(比如监听器泄漏未注销)→ 数组越来越大,写操作越来越慢
- 高频小批量写(如每秒数百次 add())→ 复制频率高,CPU 和内存带宽吃紧
- JVM 堆设置偏小,容易触发频繁的 Young GC,甚至 Promotion Failure
替代方案要看具体需求
没有银弹,选型得看瓶颈在哪:
- 如果读写比例接近,或写操作不能接受延迟,优先考虑 ConcurrentHashMap(用作键值存储)或 ConcurrentLinkedQueue
- 如果需要有序 + 高并发写 + 弱一致性可接受,Collections.synchronizedList(new ArrayList()) 配合手动分段加锁可能更可控
- 如果必须用“类似 CopyOnWrite 语义”但要更好性能,可考虑用不可变集合(如 ImmutableList from Guava)+ AtomicReference 手动管理引用更新,把复制时机收归业务控制
真正难的不是选容器,而是厘清“一致性”到底指什么:是线程安全?是顺序可见?还是业务语义上的即时生效?CopyOnWrite 把其中一部分让渡给了读性能,这个权衡点一旦模糊,就容易在压测或上线后暴露。
立即学习“Java免费学习笔记(深入)”;










