copyonwritearraylist的set方法不保证实时可见性,因写操作通过复制数组并volatile更新引用实现,读线程仍持有旧数组快照,仅在重新获取引用后才可见新值。

CopyOnWriteArrayList 的 set 方法不保证实时可见性,读操作可能看到旧值,这是它用“写时复制”换线程安全的必然代价。
set 操作本质是替换+重赋值引用
调用 set(index, element) 时,它会:
- 先复制当前数组(
Arrays.copyOf),得到新副本; - 在副本上修改指定索引位置的元素;
- 用
volatile写将内部数组引用指向新副本。
关键点在于:旧数组仍被正在执行的读线程持有,只要没完成迭代或未重新读取数组引用,就看不到这次修改。
读操作天然“快照化”,无法感知中途写入
所有读方法(get、iterator()、forEach() 等)都直接作用于当前持有的数组引用——这个引用只在写操作完成时才更新。例如:
- 一个线程正在遍历 list,此时另一个线程调用了
set(0, "new"); - 遍历线程继续读取的是旧数组,哪怕已走到索引 0,拿到的仍是旧值;
- 只有下次调用
get(0)或新建迭代器,才会读到新数组里的新值。
这不是 bug,而是设计取舍:读完全无锁,但放弃强一致性。
弱一致性对业务逻辑的真实影响
这种延迟可见性在多数场景下可接受,但需警惕以下情况:
- 依赖“写后立即读”的判断逻辑(如状态变更后马上检查是否生效);
- 多个线程通过 set 修改同一位置,且后续行为基于读结果分支;
- 与外部系统协同时,假设本地集合状态已同步更新(实际可能滞后几毫秒到更久,取决于写频次和 GC 压力)。
若需要强一致,应换用 Vector、Collections.synchronizedList,或加显式锁协调读写。
不是性能问题,而是语义契约
有人误以为“复制数组慢所以读得慢”,其实不然——读极快,写较重;真正的代价是语义上放弃了“写完即可见”。Javadoc 明确指出:“The “snapshot” style iterator method uses a reference to the state of the array at the point that the iterator was created.” 换句话说,它承诺的是快照一致性,而非实时一致性。










