应选用 CopyOnWriteArrayList 仅当读多写少、写操作稀疏(如监听器列表),因其读无锁但每次写需复制全数组,高频写将引发严重 GC 压力;迭代器基于快照故不抛 ConcurrentModificationException,但 remove() 无效;相比 Vector 和 synchronizedList,它读性能优、写代价高,且不保证强一致性与可变对象内部状态可见性。

什么时候该用 CopyOnWriteArrayList 而不是 ArrayList
它只适合「读多写少」且写操作不频繁的场景,比如监听器列表、配置订阅者集合、日志处理器注册表。一旦写操作(add、remove、set)超过每秒几次,性能就会明显下滑——每次写都复制整个数组,10 万个元素时一次 add 就可能触发几 MB 的内存分配和 GC 压力。
- 典型适用:事件总线里维护
listeners,99% 时间在遍历通知,极少增删监听器 - 典型误用:缓存淘汰队列、实时统计计数器、高频更新的 UI 数据源
- 注意:
CopyOnWriteArrayList不是线程安全的「万能替代品」,它不保证迭代期间看到最新写入 —— 迭代器基于快照,新增元素对当前迭代不可见
CopyOnWriteArrayList 的迭代器为什么不会抛 ConcurrentModificationException
因为它根本没用「modCount + expectedModCount」那套检测机制。每次调用 iterator() 都会拿到当前数组的一个不可变副本(即快照),后续所有 next()、hasNext() 都在这个副本上跑,和原列表的写操作完全隔离。
- 好处:遍历时完全不用担心其他线程在
remove,适合做“只读扫描”类逻辑 - 坑点:
Iterator.remove()是空操作,调了也没用 —— 它的remove()方法直接抛UnsupportedOperationException - 陷阱延伸:如果业务逻辑依赖「边遍历边清理」,比如过滤掉过期对象,必须改用显式索引 +
get()+ 单独收集待删项,再调removeAll()
和 Vector 或 Collections.synchronizedList() 比,差在哪
三者都能支持并发读写,但锁粒度和行为差异极大。Vector 和 synchronizedList 是方法级粗粒度锁,读写都互斥;而 CopyOnWriteArrayList 读无锁、写独占,读性能几乎无损,但写代价高。
-
Vector:每个方法都synchronized,读也要抢锁,高并发读场景下吞吐反而不如普通ArrayList+ 外部同步 -
synchronizedList:包装器模式,同样读写都串行化,且迭代时必须手动同步外部块,否则仍可能抛ConcurrentModificationException -
CopyOnWriteArrayList:读不阻塞写,写不阻塞读,但写之间严格串行 —— 如果有多个线程频繁调add,它们会排队等前一个复制完成
容易被忽略的内存与可见性细节
它的「写后读到」不是强一致性保证。由于每次写都生成新数组,旧数组可能被多个迭代器长期持有,JVM 无法及时回收,容易引发老年代堆积;同时,新数组发布依赖 volatile 写(array 字段是 volatile),所以其他线程读到新引用是安全的,但数组内对象本身的字段更新仍需自行保证可见性。
立即学习“Java免费学习笔记(深入)”;
- 常见疏忽:往
CopyOnWriteArrayList里塞了可变对象(如AtomicInteger以外的计数器),以为改了对象状态就能被所有读者看到 —— 实际上没问题,但这是对象自身责任,不是容器保证的 - GC 风险点:如果迭代器生命周期很长(比如被闭包捕获、作为回调参数传出去),对应快照数组可能驻留内存远超预期
- 别指望它解决「读写一致性」问题:A 线程 add 后立刻让 B 线程的某次 get 看到,这不能保证;B 只能保证下次 get 拿到新数组,但具体时机取决于写操作何时完成并更新
array引用










