copyonwritearraylist采用“读不加锁、写时复制”机制,适合读多写少场景:读操作无锁且线性扩展,写操作o(n)复制数组并串行执行,提供最终一致性而非强一致性,迭代器基于快照且不可修改。

CopyOnWriteArrayList 的核心在于“读不加锁、写时复制”,它用空间换时间,适合读多写少的并发场景。
读操作完全无锁,性能极高
所有读方法(如 get()、size()、iterator())都不加锁,直接访问内部数组的当前快照。由于写操作会创建新数组并原子更新引用,读线程看到的永远是某个稳定版本,不会出现脏读或并发修改异常。
- 迭代器基于构造时的数组副本,遍历时即使其他线程修改了列表,也不会抛 ConcurrentModificationException
- 读操作的响应时间恒定,不受写操作影响,吞吐量随读线程数线性增长
- 适用于配置项缓存、监听器列表、白名单等读频繁、变更极少的场景
写操作触发复制,开销集中在更新瞬间
每次增删(add()、remove()、set())都会先加锁,再复制整个底层数组,完成修改后用新数组替换旧引用。这意味着:
- 写操作时间复杂度为 O(n),与当前元素数量成正比;数组越大,复制成本越高
- 写期间其他写线程被阻塞,但读线程不受影响——这是真正的读写分离
- 写操作完成后,旧数组若无引用会被 GC 回收,需注意大对象频繁写可能加剧 GC 压力
弱一致性:读到的是“最近已完成写”的快照
CopyOnWriteArrayList 不保证实时可见性。一个写操作提交后,后续读操作不一定立刻看到结果,因为读线程可能仍持有旧数组引用(尤其在迭代过程中)。它提供的是最终一致性,而非强一致性。
- 无法用于需要“写后立即读到”的逻辑,比如计数器、状态同步等场景
- 多个写操作之间是串行的,顺序严格按加锁执行,但写与读之间没有 happens-before 保证(除通过 volatile 引用更新带来的部分语义)
- 若需强一致性,应考虑 ReentrantLock + ArrayList 或更合适的并发结构(如 ConcurrentHashMap)
不支持在迭代中修改,但允许安全遍历
它的迭代器是只读的(fail-fast 仅针对自身结构变更,而它本身不检查修改),调用 iterator.remove() 会直接抛 UnsupportedOperationException。这不是缺陷,而是设计使然——避免在快照上做原地修改,破坏不可变语义。
- 正确做法是:先收集待删除元素,再调用 removeAll() 或重新构建列表
- 若需边遍历边过滤,可用 stream().filter().collect(Collectors.toList()) 得到新列表,再整体替换
- 切勿在 foreach 循环中调用 add/remove,否则逻辑失效且易引发意料外行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











