copyonwritearraylist适合读多写少场景,因其读操作无锁、写操作复制数组并原子替换引用,保证线程安全但写代价高;适用于监听器列表、配置缓存等读远多于写的场合。

在读多写少的并发场景下,CopyOnWriteArrayList 是一个非常实用的选择——它通过“读不加锁、写时复制”的策略,让大量读操作几乎零开销,同时保证线程安全。
为什么 CopyOnWriteArrayList 适合读多写少?
它的核心机制是:所有写操作(add、remove、set 等)都会先复制一份新数组,在新数组上修改,再用 volatile 引用原子替换旧数组;而读操作(get、iterator、size 等)直接访问当前数组,全程无锁、无同步。这意味着:
- 读操作极快,且不会被写操作阻塞
- 迭代器是快照式的,遍历时即使列表被修改也不会抛
ConcurrentModificationException - 写操作代价较高(涉及数组复制和引用更新),所以写频繁时性能会明显下降
典型适用场景举例
不是所有“并发列表”都该用它,关键看是否满足“读远多于写”+“写操作不频繁”:
- 监听器/观察者列表(如事件总线中注册的回调函数,注册/注销少,触发通知极多)
- 配置项或白名单缓存(定期刷新,但运行时绝大部分时间只读取)
- 监控指标的采集点列表(新增采集点少,轮询上报频繁)
使用时的关键注意事项
用对了能提效,用错了反而埋坑:
-
不要在循环中调用
get(i)频繁索引访问——虽然不加锁,但每次 get 都是普通数组访问,没问题;真正要避免的是在迭代器里边遍历边修改(它本身就不支持,会忽略后续修改) - 写操作不是实时可见的:新元素对正在执行的读操作不可见(因为读的是旧快照),适用于“最终一致性”可接受的场景
- 内存占用略高:写操作期间新旧数组共存,极端情况下可能触发 GC 压力,大列表需谨慎
-
不支持 null 元素:构造或添加 null 会抛
NullPointerException
简单示例:安全的监听器通知
这是最经典的应用方式:
<font color="#888"><!-- 示例代码仅展示逻辑,非完整可运行类 --></font><p><code>private final CopyOnWriteArrayList<listener> listeners = new CopyOnWriteArrayList();</listener></code></p><p><code>// 注册监听器(写少)</code><br><code>listeners.add(new MyListener());</code></p><p><code>// 通知所有监听器(读多)</code><br><code>for (Listener l : listeners) { // 安全遍历,无需同步</code><br><code> l.onEvent(event);</code><br><code>}</code></p><p>这个循环里即使其他线程正在 <code>remove</code> 某个监听器,也不会影响当前遍历,也不会抛异常。</p>Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











