copyonwritearraylist通过写时复制机制实现线程安全遍历,读操作基于快照不加锁,写操作复制数组并原子替换;适用于读多写少场景,但写性能差、内存开销大,不保证强一致性。

在读多写少的并发场景中,CopyOnWriteArrayList 是 Java 提供的一个线程安全的列表实现,它通过“写时复制”(Copy-On-Write)机制,天然支持安全遍历,避免了 ConcurrentModificationException,也无需手动加锁。
为什么普通 ArrayList 遍历时会出错?
普通 ArrayList 不是线程安全的。当一个线程正在用 for-each 或迭代器遍历时,另一个线程修改了列表(如 add()、remove()),就会触发快速失败(fail-fast)机制,抛出 ConcurrentModificationException。这是因为迭代器内部维护了一个 modCount 计数器,与列表实际修改次数比对不一致所致。
CopyOnWriteArrayList 是怎么做到安全遍历的?
它的核心思想是:所有写操作(add、set、remove 等)都会先复制一份当前数组,修改副本,再用原子引用替换原数组;而读操作(get、iterator、forEach)始终基于某个快照(即某次写操作完成后的数组引用)进行,不会加锁,也不受后续写操作影响。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 迭代器返回的是只读快照,调用
iterator.remove()会抛出UnsupportedOperationException - 遍历过程中即使其他线程新增/删除元素,当前迭代器看到的仍是开始遍历时的数据状态
- 适用于读远多于写的场景——因为每次写都复制整个数组,写操作开销大,内存占用高
典型用法示例
下面是一个多线程环境下安全遍历的典型写法:
CopyOnWriteArrayList<string> list = new CopyOnWriteArrayList();
list.add("A");
list.add("B");
// 启动写线程(少量)
new Thread(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) {}
list.add("C"); // 写操作:复制+替换
}).start();
// 主线程遍历(高频)
for (String s : list) {
System.out.println(s); // 输出 A、B —— 不会包含 C,也不会报错
}</string>
注意:for-each 底层调用的是 iterator(),拿到的是遍历开始那一刻的数组快照,所以结果稳定可预期。
使用时要注意的关键点
- 不适用于实时强一致性要求的场景:遍历看不到“刚刚发生的写”,只能看到上一次写完成后的状态
- 写操作性能差、内存压力大:每次增删都要复制整个数组,大数据量时慎用
-
不能替代显式同步逻辑:如果需要“检查后执行”(如 if-not-exist-then-add),仍需额外同步或使用
ConcurrentHashMap等更合适的结构 - size() 返回的是当前快照大小,但因写操作异步替换,该值可能和下一次遍历长度不一致(不过仍是线程安全的)
不复杂但容易忽略:它解决的是“遍历过程不崩溃+数据最终一致”,而不是“每一步都强一致”。选对场景,才能真正发挥价值。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










