CopyOnWriteArrayList 是线程安全的读写分离容器,适合读多写少场景;写操作复制全数组导致开销大、内存翻倍,读操作基于快照故弱一致性,不适用于高频写或队列场景。

CopyOnWriteArrayList 是线程安全的,但只适合读多写少场景
它用写时复制(Copy-On-Write)机制实现线程安全:每次 add、remove、set 都会新建数组并复制全部元素。这意味着写操作开销大,且读操作永远看不到写操作的中间状态——因为读的是旧数组快照。
常见错误现象:Iterator 遍历时调用 list.remove() 不抛 ConcurrentModificationException,但也不会生效;因为迭代器基于创建时的数组副本,修改的是另一份数组。
- 适用场景:监听器列表、配置白名单、日志收集器等几乎不变更的集合
- 不适用场景:高频增删、实时性要求高的计数器或队列
- 注意
size()和get(int)是无锁的,但它们返回的值可能已“过期”——写操作提交后,读操作仍可能看到旧长度或旧值
和 Vector / Collections.synchronizedList 的关键区别在哪
Vector 是方法级 synchronized,所有操作串行化;Collections.synchronizedList 本质也是包装了一层同步块。二者读写都阻塞,吞吐量低,但能保证强一致性(比如 size() 和后续 get(0) 一定匹配)。
CopyOnWriteArrayList 则是读写分离:读完全无锁,写独占复制。性能表现截然不同:
立即学习“Java免费学习笔记(深入)”;
本程序源码为asp与acc编写,并没有花哨的界面与繁琐的功能,维护简单方便,只要你有一些点点asp的基础,二次开发易如反掌。 1.功能包括产品,新闻,留言簿,招聘,下载,...是大部分中小型的企业建站的首选。本程序是免费开源,只为大家学习之用。如果用于商业,版权问题概不负责。1.采用asp+access更加适合中小企业的网站模式。 2.网站页面div+css兼容目前所有主流浏览器,ie6+,Ch
- 100 个线程并发读 + 1 个线程写:CopyOnWriteArrayList 吞吐远高于 synchronizedList
- 10 个线程频繁写:CopyOnWriteArrayList 可能比 synchronizedList 慢一个数量级,尤其集合较大时
- 内存占用:写操作期间新老数组共存,瞬时内存翻倍,GC 压力明显
为什么不能用 CopyOnWriteArrayList 实现生产者-消费者队列
它不支持阻塞等待、容量控制、超时获取等队列核心语义。调用 remove(0) 删除首元素时,会触发整数组复制;而 poll() 类操作需 O(n) 时间查找+复制,违背队列的 O(1) 出队期望。
更严重的问题是语义错位:
-
isEmpty()返回true后,下一刻可能已有元素被写入,但你无法感知 - 没有类似
BlockingQueue的take(),无法挂起消费者线程等待数据 - 遍历中删除元素不会影响当前迭代器,但你无法知道哪些元素“刚刚被其他线程删掉”
CopyOnWriteArrayListlist = new CopyOnWriteArrayList<>(); list.add("a"); list.add("b"); // 这个迭代器看到的是 ["a", "b"] 的快照 for (String s : list) { System.out.println(s); if ("a".equals(s)) list.remove("b"); // 删除成功,但循环里仍会输出 "b" }
替代方案选型:什么时候该换别的并发容器
别为了“线程安全”硬套 CopyOnWriteArrayList。真实业务中,多数集合操作需要的是“最终一致”或“弱一致性”,而非“读写强一致”。根据场景选更合适的工具:
- 需要高并发读 + 少量写 + 元素不多 →
CopyOnWriteArrayList可以 - 需要频繁插入/删除中间位置 → 改用
ConcurrentLinkedQueue或ConcurrentSkipListSet - 需要键值映射 + 并发访问 →
ConcurrentHashMap(注意它不保证遍历一致性) - 需要阻塞、限流、超时 → 必须上
ArrayBlockingQueue或LinkedBlockingQueue
最容易被忽略的一点:很多所谓“并发问题”,其实源于对共享状态的过度依赖。比起选容器,先考虑能否把状态局部化(ThreadLocal)、事件化(Disruptor)、或者函数式不可变(immutables)。容器只是工具,不是万能解药。









