CopyOnWriteArrayList适用于读多写少、无需强一致性的并发场景,读操作无锁高效,写操作复制数组开销大;不适用于高频增删、内存敏感或需实时一致性的场景。

CopyOnWriteArrayList适合写少读多的并发场景
它不是万能的线程安全替代品,只在特定模式下表现良好:读操作远多于写操作,且对写操作的实时性不敏感。底层每次写(add、remove、set)都会复制整个数组,读则直接访问当前快照,所以读不加锁、无阻塞。
常见误用现象:for-each遍历时调用remove()导致ConcurrentModificationException——这不是bug,而是设计使然:迭代器基于创建时的数组快照,无法反映后续写操作,也不支持修改。
- 适用场景:监听器列表、配置项缓存、状态广播集合(如注册的回调函数)
- 不适用场景:高频增删、内存受限、需要强一致性(如账户余额列表)
- 写操作延迟可见:新线程在写入后可能仍读到旧快照,直到下次读取到新数组引用
与Vector和Collections.synchronizedList对比的关键差异
Vector和Collections.synchronizedList(new ArrayList())都靠同步块串行化所有操作,读写互斥;而CopyOnWriteArrayList读完全无锁,写之间互斥但不阻塞读。这意味着在读密集场景下吞吐量明显更高,但写代价陡增。
性能陷阱:单次add()触发数组复制,时间复杂度为O(n);若在循环中反复add,会多次复制,开销爆炸。应改用addAll()批量操作。
立即学习“Java免费学习笔记(深入)”;
-
Vector:方法级synchronized,读写都等锁,扩展性差 -
Collections.synchronizedList:装饰器模式,需手动同步迭代(synchronized(list) { ... }),否则仍可能ConcurrentModificationException -
CopyOnWriteArrayList:迭代器天然安全,无需额外同步,但内存占用翻倍(旧数组待GC)
迭代器不支持remove()和add()的真实原因
它的Iterator是只读快照,所有修改方法(remove()、add())都抛出UnsupportedOperationException。这不是疏漏,而是强制语义:你不能通过迭代器改变“此刻看到的数据”,因为那会破坏快照一致性。
如果真需要边遍历边删除符合条件的元素,必须改用普通for倒序索引或收集待删ID再批量处理:
CopyOnWriteArrayListlist = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c")); // ✅ 正确:收集后批量移除 List toRemove = new ArrayList<>(); for (String s : list) { if (s.startsWith("a")) toRemove.add(s); } list.removeAll(toRemove); // ❌ 错误:迭代器remove()直接抛异常 // for (String s : list) { list.remove(s); } // UnsupportedOperationException
容易被忽略的内存与GC压力
每次写操作生成新数组,旧数组留在堆中等待GC。若写操作频繁或元素对象大(如含长字符串、嵌套集合),会导致老年代快速堆积,引发Full GC。监控时注意CopyOnWriteArrayList相关对象的存活时间与大小分布。
没有内置的“紧凑”机制,也不能手动触发数组收缩。一旦扩容过,即使后续大量删除,数组长度也不会缩小。
- 建议限制其容量预期:初始化时用
new CopyOnWriteArrayList(initialCapacity)避免多次扩容复制 - 避免存储大对象或未清理的引用(如监听器未注销,导致本该被回收的对象持续被数组引用)
- JDK 17+ 中仍无结构变更优化,该类设计目标始终是“读绝对优先”,不是通用并发容器










