为何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迭代机制:
- 它的底层还是普通的HashSet,
modCount机制依然存在。 - 迭代是一个跨多个方法的过程(
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
相关产品推荐
相关产品推荐

