CopyOnWriteArraySet通过写时复制机制天然保证元素唯一性,底层复用CopyOnWriteArrayList实现去重:add前先contains判断,仅不存在时才添加;读线程安全、遍历不抛ConcurrentModificationException,适合读多写少场景。

CopyOnWriteArraySet 通过“写时复制”机制,在并发环境下天然保证元素唯一性,不依赖额外锁或哈希冲突处理,核心在于底层复用 CopyOnWriteArrayList 的线程安全数组 + 去重逻辑。
底层结构:委托 CopyOnWriteArrayList 实现唯一性
CopyOnWriteArraySet 并非独立维护哈希表或红黑树,而是内部持有一个 CopyOnWriteArrayList 实例作为唯一存储载体。所有 add、remove 等操作最终都转为对这个 list 的调用,并在插入前显式检查是否已存在:
- add(E e) 方法先调用 al.contains(e) 判断是否重复;仅当返回 false 时,才执行 al.add(e)
- 由于底层 list 是线程安全的,contains 和 add 之间的状态不会被其他线程干扰(虽然不是原子组合,但 write-lock 保证了单次 add 的完整性)
- 整个过程无 synchronized 块或 CAS 循环,也不引入 ConcurrentMap 等重型结构
写时复制如何支撑去重行为
每次 add 操作触发一次完整的写流程,这恰好成为去重的“安全窗口”:
- 获取 ReentrantLock,确保同一时刻最多一个线程执行写入
- 读取当前数组快照,调用 contains() 遍历旧数组判断是否存在——这个判断基于稳定、不可变的旧副本
- 若不存在,则拷贝数组、追加新元素、替换引用;整个过程对读线程完全透明
- 即使多个线程几乎同时 add 相同元素,也只会有一个成功写入新数组,其余因 contains 返回 true 而跳过
读多写少场景下的唯一性保障特点
它的“唯一性”不是强实时一致,而是与写时复制模型匹配的弱一致性唯一:
- 迭代遍历时看到的是创建迭代器那一刻的快照,期间新增元素不会出现在本次遍历中,也不会破坏已有遍历的安全性
- 两个线程先后 add 同一元素,后者的 contains 判断可能仍基于较旧数组,但最终结果仍是集合中只保留一份——因为写操作互斥且基于最新可见状态判断
- 没有全局哈希桶锁或 rehash 过程,避免了 HashMap 在并发扩容时的死循环风险
和 HashSet / ConcurrentHashMap.KeySet 的关键区别
它不追求 O(1) 插入,而是用可预测的 O(n) 换取零同步读与绝对遍历安全:
- HashSet 在多线程下直接使用会丢失元素或抛出异常;包装成 Collections.synchronizedSet 后,遍历时仍需手动同步,否则可能报 ConcurrentModificationException
- ConcurrentHashMap.KeySet 虽支持并发,但 keySet() 返回的 Set 视图在迭代时仍可能反映部分更新,且 remove 操作可能失败(取决于 segment 锁粒度)
- CopyOnWriteArraySet 的 add/remove/iterator 全部开箱即用,无需调用方协调锁,特别适合事件监听器注册、配置白名单等写极少、读极频繁、要求遍历绝对不中断的场景










