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() 可能仍返回旧值,直到下一次写操作触发新数组发布声明时无需特殊配置,但要注意构造方式影响初始容量:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
// 推荐:如果已知大概大小,用带 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")); // 注意:这是原子操作,但内部仍复制</string></string>
容易被忽略的线程安全边界
CopyOnWriteArrayList 仅保证自身结构变更的线程安全,不保护元素本身的并发访问。例如:
- 若存入的是可变对象(如
AtomicInteger以外的普通Counter),多个线程同时调用counter.inc()仍需额外同步 -
get(i)返回的对象引用,其内部状态修改不属于该集合的保护范围 - 复合操作(如“检查是否存在再添加”)不是原子的:
!list.contains(x) && list.add(x)仍可能重复插入
真正需要强一致性的写场景,得换 ConcurrentHashMap 或显式锁,别硬套 CopyOnWriteArrayList。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










