copyonwritearraylist写操作慢是因为每次add/set/remove都要复制整个底层数组,导致cpu和gc压力双升;数组大时单次add可能分配几十mb临时对象;迭代器基于快照,修改不可见且不抛concurrentmodificationexception。

CopyOnWriteArrayList 写操作为什么特别慢
因为每次 add、set、remove 都要复制整个底层数组。不是增量更新,是「原数组 → 新数组 → 替换引用」三步全走完才返回。
- 写多的场景(比如高频上报、实时计数器)会明显拖慢吞吐,CPU 和 GC 压力双升
- 如果数组本身很大(比如 10 万+ 元素),一次
add可能触发几十 MB 的临时对象分配 - 注意
CopyOnWriteArrayList的size()是 O(1),但contains()是 O(n) 且遍历的是快照,查不到刚写入的新值
迭代时修改不报 ConcurrentModificationException 却读不到新数据
这是设计使然,不是 bug。迭代器持有的是构造时的数组快照,后续所有写操作都影响不到它。
- 常见误用:在 for-each 循环里调
list.remove(x),以为能删掉当前项——实际删的是另一个副本,循环仍按旧快照跑完 - 更隐蔽的问题:两个线程 A 迭代、B 写入,A 看不到 B 的任何变更,哪怕 B 已执行完 5 秒
- 没有「弱一致性」兜底,只有「最终一致性」,而这个「最终」取决于下一次读操作是否拿到新引用——中间可能有毫秒级甚至更长的窗口
内存占用翻倍不是理论值,是真实发生的常驻开销
CopyOnWriteArrayList 在写入瞬间必然存在新旧两个数组同时被 GC root 引用,直到旧数组彻底不可达。
- JVM 无法对「正在被迭代的旧数组」做回收,哪怕只差最后一个迭代器结束
- 如果迭代器生命周期长(比如传给下游异步处理、或被意外持有),旧数组可能驻留数十秒,内存直接 double
- 对比
ConcurrentHashMap:它用分段锁+volatile,写操作不复制整个结构,内存更友好
什么场景下宁可不用 CopyOnWrite 容器
当「读多写少」这个前提被打破,或者「读写之间不能容忍延迟」时,它就从解药变成毒药。
立即学习“Java免费学习笔记(深入)”;
- 实时风控规则列表:规则动态增删频繁,且下游必须立刻感知变更——改用
ConcurrentLinkedQueue+ 版本号轮询,或AtomicReference<list></list>手动控制可见性 - 日志采集缓冲区:每秒 add 几千条,很快 OOM——换成
ArrayBlockingQueue或无锁 RingBuffer - 配置监听器集合:注册/注销较频繁,且要求监听器回调顺序严格——
CopyOnWriteArrayList的写顺序不保证对所有读线程立即可见,建议用ReentrantLock+ 普通ArrayList
真正关键的不是「它线程安全」,而是「它的线程安全是以牺牲写性能、内存、实时性换来的」。用之前先看写操作频率和延迟敏感度。










