CopyOnWriteArrayList 适合读远多于写、写操作不频繁且允许读取旧快照的场景,如配置监听器列表、事件回调队列、白名单缓存;写频繁时因数组复制导致性能陡降,不可替代普通 ArrayList 作通用容器。

CopyOnWriteArrayList 适合什么场景
它只在明确满足「读远多于写、写操作不频繁、允许读取到旧快照」时才值得用。比如配置监听器列表、事件回调队列、白名单缓存——这些地方读取频次高,但增删元素极少,且偶尔读到上一次写入前的状态完全可接受。
一旦写操作变多(比如每秒几十次 add() 或 remove()),CopyOnWriteArrayList 就会因频繁数组复制导致 CPU 和内存飙升,比直接加锁的 ArrayList + synchronized 还慢。
为什么不能用它替代普通 ArrayList 做通用容器
-
CopyOnWriteArrayList 的 get(int) 是 O(1),但 add(E)、remove(Object)、set(int, E) 全是 O(n) —— 每次都复制整个底层数组
- 迭代器不支持
remove():调用会抛 UnsupportedOperationException,因为迭代基于快照,无法反向修改原结构
- 它不保证实时一致性:线程 A 调用
add() 后,线程 B 紧接着 size() 可能仍返回旧值,直到下一次写操作触发新数组发布
- 内存占用翻倍风险:写操作期间,新老数组同时存在,GC 压力明显高于普通集合
正确初始化和常用操作示例
CopyOnWriteArrayList 的 get(int) 是 O(1),但 add(E)、remove(Object)、set(int, E) 全是 O(n) —— 每次都复制整个底层数组remove():调用会抛 UnsupportedOperationException,因为迭代基于快照,无法反向修改原结构add() 后,线程 B 紧接着 size() 可能仍返回旧值,直到下一次写操作触发新数组发布声明时无需特殊配置,但要注意构造方式影响初始容量:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
// 推荐:如果已知大概大小,用带 initialCapacity 的构造函数减少早期复制
List<String> listeners = new CopyOnWriteArrayList<>(16);
// 不推荐:空参构造,首次 add 就要分配默认 16 元素数组,后续扩容又复制
List<String> bad = new CopyOnWriteArrayList<>();
// 安全遍历(不会抛 ConcurrentModificationException)
for (String l : listeners) {
l.handle(); // 注意:这里调用的是旧快照里的对象引用
}
// 写操作示例:每次都会生成新数组
listeners.add("logger");
listeners.removeIf(s -> s.startsWith("debug")); // 注意:这是原子操作,但内部仍复制
容易被忽略的线程安全边界
CopyOnWriteArrayList 仅保证自身结构变更的线程安全,不保护元素本身的并发访问。例如:
立即学习“Java免费学习笔记(深入)”;
- 若存入的是可变对象(如
AtomicInteger以外的普通Counter),多个线程同时调用counter.inc()仍需额外同步 -
get(i)返回的对象引用,其内部状态修改不属于该集合的保护范围 - 复合操作(如“检查是否存在再添加”)不是原子的:
!list.contains(x) && list.add(x)仍可能重复插入
真正需要强一致性的写场景,得换 ConcurrentHashMap 或显式锁,别硬套 CopyOnWriteArrayList。


















