copyonwritearrayset 迭代器不抛 concurrentmodificationexception 是因其返回构造时刻的不可变快照,基于底层数组复制实现;但遍历时无法感知写操作、不支持 remove()、写开销大,仅适用于读多写少、大小适中、可接受弱一致性的场景。

CopyOnWriteArraySet 本身不抛 ConcurrentModificationException,但它的迭代器是弱一致性的快照,不是实时视图——这点常被误认为“完全安全”,实际使用中仍有关键限制。
为什么遍历时不会抛 ConcurrentModificationException
因为 CopyOnWriteArraySet 的 iterator() 返回的是构造时刻集合内容的不可变快照。底层基于 CopyOnWriteArrayList 实现,每次写操作(add、remove)都会复制整个底层数组,而迭代器始终遍历旧数组副本。
- 写操作开销大:频繁
add/remove会触发大量数组复制,CPU 和内存压力明显上升 - 迭代器看不到写操作:在迭代过程中调用
add或remove,当前迭代器绝不会反映这些变更 - 无法在迭代中调用
remove():它的Iterator.remove()方法直接抛UnsupportedOperationException
什么时候适合用 CopyOnWriteArraySet
适用于读多写少、且对读取实时性要求不高的场景,比如监听器注册表、配置白名单缓存。
- 读操作占比 > 95%,写操作极少(如应用启动时初始化,运行期几乎不变)
- 能接受“迭代期间看不到新增/删除”的语义(例如广播事件前快照所有监听器,之后增删不影响本次广播)
- 集合大小稳定在几百以内;超过 1000 元素时,单次
add复制成本显著升高
替代方案对比:ConcurrentHashMap + keySet() vs CopyOnWriteArraySet
如果需要支持高频写入 + 安全迭代,ConcurrentHashMap.newKeySet()(Java 8+)往往更合适:
-
ConcurrentHashMap.newKeySet()返回的Set支持并发修改,迭代器是弱一致性但不复制整个结构,性能更平稳 - 它允许
null元素(CopyOnWriteArraySet不允许) - 注意:
newKeySet()的迭代器仍可能跳过某些写入(取决于分段锁时机),但不会因并发修改而异常 - 若需强一致性(如必须看到所有已提交写入),只能加显式同步或改用
Collections.synchronizedSet()+ 手动synchronized块保护迭代
一个典型误用示例与修复
下面代码看似安全,实则逻辑错误:
CopyOnWriteArraySet<string> set = new CopyOnWriteArraySet(Arrays.asList("a", "b"));
for (String s : set) {
if ("a".equals(s)) {
set.remove("b"); // ✅ 不抛异常,但本次循环仍会输出 "b"
}
}
// 输出:a b —— remove 生效了,但迭代器看不到</string>
若业务依赖“边遍历边清理”,应改用:
- 收集待删元素后批量
removeAll(适用于写不频繁) - 改用
ConcurrentHashMap.newKeySet()并接受弱一致性语义 - 或直接用
Iterator配合ConcurrentLinkedQueue等无锁结构做消费队列
真正难处理的从来不是异常本身,而是“没异常却结果不对”——CopyOnWriteArraySet 的快照语义容易掩盖竞态逻辑缺陷。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











