copyonwritearraylist适合读多写少场景,因其读操作无锁、写操作通过复制数组实现线程隔离;读吞吐高,写开销大,迭代器基于快照且不抛concurrentmodificationexception。

CopyOnWriteArrayList 是 Java 并发包中专为“读多写少”场景设计的线程安全列表,它的核心思想是:**读操作完全无锁,写操作通过复制底层数组实现线程隔离**。适合读操作远多于写操作、且对实时一致性要求不高的场景(如监听器列表、配置项缓存)。
为什么 CopyOnWriteArrayList 适合读多写少?
它内部持有一个 volatile 数组引用,所有读操作(get、iterator、size 等)直接访问当前数组,不加锁、不阻塞;而每次写操作(add、remove、set)都会先复制一份新数组,在新数组上修改,再用 CAS 原子更新引用指向新数组。这意味着:
- 读操作零同步开销,高并发下吞吐量极高
- 写操作代价大(复制+GC压力),频繁写会导致性能骤降
- 迭代器基于快照,遍历时即使列表被修改也不会抛 ConcurrentModificationException,但看不到写操作的最新结果
典型使用方式(避免常见陷阱)
声明和初始化很简单:
CopyOnWriteArrayList
但要注意以下关键点:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 不要在迭代过程中调用 remove() 或 add():迭代器是只读快照,其 remove() 方法会抛 UnsupportedOperationException;想删除需改用 list.remove(obj)
- 写操作后读到的是“最终一致”结果,不是强一致:t1 添加元素后,t2 立即 get(size()-1) 可能还读不到,因为引用更新有微小延迟(虽通常纳秒级)
- 避免用它做高频计数或累加:比如用 size() 做循环条件 + 在循环中写入,可能因 size 滞后导致逻辑错误
一个实用示例:事件监听器管理
这是 CopyOnWriteArrayList 的经典用法——注册/通知监听器,读(遍历通知)极多,写(增删监听器)极少:
public class EventManager {
private final CopyOnWriteArrayList
public void addListener(Listener l) { listeners.add(l); }
public void removeListener(Listener l) { listeners.remove(l); }
public void fireEvent(Event e) {
// 安全遍历,无需同步,不会因其他线程移除监听器而失败
for (Listener l : listeners) {
l.onEvent(e);
}
}
}
这里即使有 1000 个读线程同时 fireEvent,也不会阻塞;而每秒仅几次 add/remove,开销可接受。
对比其他线程安全列表
简单对比帮你判断是否该选它:
- Vector / Collections.synchronizedList():读写都加同一把锁,读多时严重串行化,吞吐低
- ConcurrentHashMap 替代方案(如用 key 做索引):适合需要随机写+读,但不提供 List 语义(如按序遍历、下标访问)
- 普通 ArrayList + 外部读写锁(ReentrantReadWriteLock):读写分离,但读锁仍需获取/释放,比 CopyOnWriteArrayList 略重;且需手动管理锁,易出错
结论:当明确满足“读远多于写 + 不需要写后立即可见 + 迭代安全性优先”时,CopyOnWriteArrayList 是简洁高效的选择。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










