copyonwritearraylist适合读远多于写、写不频繁、列表规模小的场景;其快照迭代器导致遍历时无法感知并发修改,且多次get可能看到不同版本数据,故无法保障实时一致性。

CopyOnWriteArrayList 适合什么场景
它只在写操作(add、remove、set)时复制整个底层数组,读操作(get、iterator、size)完全无锁。因此,**只有当读远多于写、且写操作不频繁(比如每秒最多几次)、列表长度不太大(几百到几千元素)时,它才比 Vector 或 Collections.synchronizedList 更高效**。如果写操作密集,或列表常达上万元素,复制开销会明显拖慢性能。
为什么不能用它做实时一致性保障
CopyOnWriteArrayList 的迭代器是快照式的——调用 iterator() 时就固定了当时数组的副本,后续其他线程的写操作对这个迭代器完全不可见。这意味着:
- 遍历时无法感知新增/删除,哪怕只差几毫秒
-
ConcurrentModificationException永远不会抛出(因为没用 modCount 机制) - 多个连续
get(i)调用之间,也可能看到“中间态”:比如第 0 个是旧值,第 1 个是新值(因两次 get 分别命中不同版本数组)
所以它不适合需要强一致读的逻辑,比如基于当前全量状态做原子判断(如“若列表为空则触发初始化”)。
写操作的阻塞与可见性边界
每次写都会加锁(ReentrantLock),但锁粒度是整个列表,不是单个元素。关键点在于:
- 写操作之间严格串行,但写和读完全不互斥
- 一个写操作完成后,其修改对所有后续的读操作立即可见(注意:不是“立刻”,而是下一个读操作开始时看到新数组)
- 没有类似
volatile的字段级语义;可见性靠数组引用的 volatile 写(array = newArray)保证 - 不要在写操作中做耗时任务(如 IO、远程调用),否则会卡住所有其他写请求
示例:以下代码不会报错,但 iter 看不到 list.add("b") 的结果:
CopyOnWriteArrayList<string> list = new CopyOnWriteArrayList(Arrays.asList("a"));
Iterator<string> iter = list.iterator();
list.add("b"); // 这次 add 复制了新数组,但 iter 仍指向旧数组
while (iter.hasNext()) {
System.out.println(iter.next()); // 只输出 "a"
}</string></string>
替代方案怎么选:不是所有“读多写少”都该用它
如果只是需要安全遍历+偶尔修改,且能接受最终一致性,CopyOnWriteArrayList 是简单选择。但要注意:
- 如果写后必须立刻被所有读线程看到(例如配置刷新),应改用
AtomicReference<list>></list>+ 不可变列表(如ImmutableList) - 如果读写频率接近,或需复杂结构操作(如按条件批量删),
ConcurrentHashMap模拟列表(key=index, value=element)或ConcurrentLinkedQueue更合适 - JDK 9+ 中,若只需不可变视图,优先考虑
List.copyOf()配合普通ArrayList,避免引入并发类的额外开销
真正容易被忽略的是内存放大:每次写都新建数组,老数组只能等 GC 回收。在堆压力敏感或长时间运行的服务里,这可能比锁竞争更早成为瓶颈。










