copyonwritearraylist 的 set 方法不触发写时复制,而是在已创建的副本上直接替换元素;它通过加锁、复制数组、修改副本、原子更新引用四步完成线程安全更新,读操作不受影响。

CopyOnWriteArrayList 的 set 方法本身不会触发写时复制,它只是在当前数组副本上直接替换元素——因为此时已有可写的副本(写操作前已通过加锁 + 数组复制完成“写时复制”准备)。
set 不是触发写时复制的入口,而是写时复制后的安全更新步骤
CopyOnWriteArrayList 的“写时复制”发生在每次**结构性修改操作开始时**(如 add、remove、set),但 set 的特殊之处在于:它不改变集合大小或结构,只替换已有位置的值,所以只需锁定 + 读取当前数组 + 复制 + 修改 + 替换引用。整个过程分三步:
- 获取独占锁(ReentrantLock),确保同一时刻只有一个写线程执行
- 获取当前内部数组(volatile 修饰的 array),用 Arrays.copyOf 创建新数组
- 在新数组指定索引处赋新值,再用 setArray() 原子替换内部引用
为什么 set 能线程安全地替换而不影响读操作
因为读操作(get、iterator 等)全程无锁,且直接访问当前 array 引用指向的数组。而 set 创建的是一个全新数组副本,旧数组仍被正在读的线程持有,直到它们自然完成遍历。例如:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
线程 B 此时正在 for-each 遍历,仍看到 [A,B,C,D];下一次 get(2) 才会读到 "X"
手动触发写时复制?不需要,set 已内置完整流程
你无需也不应该绕过 set 方法去“手动复制”。只要调用 public E set(int index, E element),框架就自动完成:
- 校验索引是否越界(IndexOutOfBoundsException)
- 加锁防止并发写冲突
- 复制当前数组(底层调用 Arrays.copyOf)
- 在副本中修改指定下标元素
- 原子更新 array 引用(通过 setArray,使用 volatile 写保证可见性)
注意:set 的返回值是被替换的旧元素
和 List 接口定义一致,set 返回原位置的旧值,可用于判断或日志记录。例如:
String old = list.set(0, "newFirst"); // old 是替换前的元素,"newFirst" 已存在于新数组中Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










