copyonwritearraylist适用于读多写少场景,读操作无锁高性能,写操作加锁复制数组导致o(n)开销;适合监听器列表等只读频繁场景,但不保证复合操作原子性且需注意内存与gc压力。

CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent)中为“读多写少”场景量身设计的线程安全列表。它不加锁读取,靠写时复制(Copy-On-Write)机制规避并发冲突——每次修改都新建数组副本,读操作永远在不可变快照上进行,天然无锁、无竞争。
读操作完全无锁,性能极高
所有读方法(get()、size()、iterator()、forEach() 等)直接访问内部数组引用,不加任何同步。即使多个线程同时遍历,也不会阻塞或抛 ConcurrentModificationException(因为迭代器基于创建时的数组快照)。
- 适合高频遍历、查询、监听等只读场景,比如事件监听器列表、配置项缓存、状态快照收集
- 注意:
iterator()返回的是弱一致性迭代器——它看不到写操作的最新结果,但绝不会出错或崩溃
写操作开销明确,仅适用于低频修改
每次添加、删除、更新(如 add()、remove()、set())都会:
- 加锁(使用内部
ReentrantLock) - 复制当前数组(
Arrays.copyOf()) - 在副本上修改,再用新数组替换旧引用(原子赋值)
这意味着写操作时间复杂度为 O(n),内存占用翻倍(旧数组待 GC)。所以绝不适合频繁增删的场景,比如高频计数器、实时消息队列。
正确使用的关键细节
-
不能用于替代同步逻辑:它不保证复合操作的原子性。例如
if (!list.contains(x)) list.add(x)仍可能重复添加,需额外同步或改用ConcurrentHashMap等结构 -
迭代期间允许写,但不影响当前迭代:新线程的写操作生成新数组,正在执行的
for-each仍遍历旧数组,安全但“滞后” - 初始化建议预估容量:虽然支持动态扩容,但每次写都复制整个数组,初始容量设得合理可减少复制次数和内存浪费
-
注意内存与 GC 压力:大量写操作会快速产生短生命周期大数组,可能触发频繁 Young GC,监控
Old Gen和 GC 日志很有必要
一个典型适用示例
维护一组注册的监听器:
private final CopyOnWriteArrayList<eventlistener> listeners = new CopyOnWriteArrayList();
public void addListener(EventListener l) {
listeners.add(l); // 写少,安全
}
public void fireEvent(Event e) {
// 读多:遍历过程无需锁,且不会因其他线程添加监听器而失败
for (EventListener l : listeners) {
l.onEvent(e);
}
}</eventlistener>
这里 fireEvent 可被成百上千次调用,而 addListener 可能只发生几次——正是 CopyOnWriteArrayList 的最佳舞台。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











