copyonwritearrayset写操作慢因每次add/remove需复制整个底层数组(o(n)),适合写少读多场景;迭代器基于快照,遍历时修改无效;与synchronizedset相比,其读无锁、写独占、迭代免锁。

CopyOnWriteArraySet 写操作为什么慢得明显
因为每次 add、remove 都会复制整个底层数组 —— 它底层用的是 CopyOnWriteArrayList,而数组复制是 O(n) 操作。写少读多的场景才适合它,比如监听器列表、配置白名单缓存。
- 10 万元素时一次
add可能触发几 MB 内存分配 + 数组拷贝,GC 压力肉眼可见 - 写操作期间不阻塞读,但新写入的内容对「正在执行的迭代器」不可见(迭代器基于快照)
- 如果写操作频繁(比如每秒上百次),不如换成
ConcurrentHashMap+keySet()模拟 Set 语义
遍历时修改导致的“假删除”或“漏遍历”
用增强 for 或 iterator() 遍历 CopyOnWriteArraySet 时,调用 remove 不会抛 ConcurrentModificationException,但也不会影响当前迭代器——因为迭代器在构造时已持有数组快照。
- 现象:循环中写
set.remove(x),看似删了,但下一轮迭代仍可能看到x(其实是旧快照里的残留) - 正确做法:不要边遍历边改;真要过滤,先收集待删项,遍历完再批量
removeAll - 错误示例:
for (String s : set) { if (s.startsWith("tmp")) set.remove(s); }→ 这段代码逻辑上想删,实际无效
和 Collections.synchronizedSet 的关键区别在哪
两者都线程安全,但锁粒度和行为完全不同:Collections.synchronizedSet 是方法级 synchronized,读写都串行;CopyOnWriteArraySet 读无锁、写独占、迭代免锁。
- 读多写少且迭代频繁 → 选
CopyOnWriteArraySet - 写稍多、或需强一致性(比如要求迭代器立刻反映最新状态)→ 选
synchronizedSet或ConcurrentSkipListSet -
size()在CopyOnWriteArraySet中返回的是快照大小,但写操作刚完成时,它未必等于「当前真实元素数」(因写操作尚未完成复制)——不过这个误差仅存在于写操作执行中的极短窗口,通常可忽略
初始化时传入 ArrayList 为什么可能丢数据
CopyOnWriteArraySet(Collection) 构造函数内部会调用 addAll,而 addAll 对传入集合逐个 add —— 如果源集合本身含重复元素,会被自动去重;但如果源集合是未排序的 ArrayList,且你依赖插入顺序做后续处理,那要注意:它底层转成数组后不再维护原始顺序语义(虽然巧合下常一致,但不保证)。
立即学习“Java免费学习笔记(深入)”;
- 常见误用:
new CopyOnWriteArraySet(Arrays.asList("a", "b", "a"))→ 结果只有 2 个元素,不是 3 个 - 若必须保序且去重,先用
LinkedHashSet做预处理:new CopyOnWriteArraySet(new LinkedHashSet(list)) - 别指望它替代
TreeSet:不支持排序,也不实现NavigableSet










