You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Collection.synchronized(set)仍抛出ConcurrentModificationException?

为什么Collections.synchronizedSet迭代删除仍会抛出ConcurrentModificationException?

先搞懂ConcurrentModificationException的本质

这个异常源于集合的**快速失败(fail-fast)**机制:

  • 集合内部维护一个modCount变量,记录自身被修改的次数(增、删、改操作都会触发计数)。
  • 当你创建迭代器时,迭代器会保存当前的modCount值到expectedModCount。
  • 迭代过程中(比如调用hasNext()、next()),迭代器会对比modCount和expectedModCount,如果不一致就抛出异常,以此避免迭代时集合结构变化导致的不可预期行为。

CopyOnWrite系列集合为什么不会抛异常?

CopyOnWriteArrayList/CopyOnWriteArraySet的核心是写时复制:

  • 所有修改操作(add/remove)都会复制一份全新的底层数组,修改操作在新数组上完成,之后再替换原数组。
  • 迭代器是基于原数组的快照创建的,迭代过程中即使集合被修改,迭代器访问的还是旧数组,根本不会去对比modCount,自然不会触发异常。

Collections.synchronizedSet的问题出在哪?

Collections.synchronizedSet()只是给原集合(比如HashSet)的所有方法套了一层同步锁,保证多线程下单个方法的原子性,但完全没有改变原集合的fail-fast迭代机制:

  1. 它的底层还是普通的HashSet,modCount机制依然存在。
  2. 迭代是一个跨多个方法的过程(hasNext()、next()),哪怕是单线程,如果你在迭代时直接调用集合的remove()方法(而非迭代器的remove()),会直接修改集合的modCount,但迭代器的expectedModCount没同步更新,下一次迭代操作就会检测到不一致,抛出异常。

举个会抛异常的错误示例:

Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
syncSet.add("a");
syncSet.add("b");

// 增强for循环底层是迭代器,但直接调用集合的remove
for (String s : syncSet) {
    syncSet.remove(s);
}

正确的操作方式(不会抛异常):

Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
syncSet.add("a");
syncSet.add("b");

Iterator<String> iterator = syncSet.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    iterator.remove(); // 用迭代器的remove方法,会同步更新expectedModCount和modCount
}

总结

  • CopyOnWrite系列通过快照迭代+写时复制绕过了fail-fast的检查逻辑。
  • Collections.synchronizedXXX仅保证方法级别的线程安全,没有改变原集合的迭代机制,迭代时必须通过迭代器自身的remove()方法修改,否则依然会触发ConcurrentModificationException。

内容的提问来源于stack exchange,提问作者Parthiban Ramasamy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 22:40:39