跳转到主内容
极星编程网:以代码为星,赴技术山海!

什么是Java并发容器的弱一致性迭代器_遍历过程中允许修改且不抛出ConcurrentModificationException

ConcurrentHashMap 迭代器不抛 ConcurrentModificationException 是因为其采用弱一致性设计,基于分段结构和 volatile 读实现非阻塞遍历,不校验 modCount;可能漏读、重复或返回 null,但保证已存在元素不消失。 为什么
ConcurrentHashMap
的迭代器不抛
ConcurrentModificationException
因为它的迭代器是弱一致性的——不是“实时快照”,也不是“完全隔离”,而是基于内部分段结构和 volatile 读的“尽力而为”遍历。它不阻塞写操作,也不校验修改计数(
modCount
),所以即使你一边
put()
一边
iterator().next()
,也不会触发异常。 但注意:这不等于“安全看到所有修改”。它可能漏掉刚插入的元素,也可能重复看到某个元素(尤其在扩容时),甚至返回
null
值(如果遍历到正在被移除的节点)。
ConcurrentHashMap
迭代器从不加锁,只依赖
volatile
变量和 UNSAFE 操作读取链表头、跳表层级等 遍历时若发生扩容,迭代器会尝试切换到新表,但切换过程本身不原子,导致中间状态可见 弱一致性仅保证“已存在的元素不会突然消失”,不保证“新增/删除立即可见”
CopyOnWriteArrayList
迭代器也弱一致,但原理完全不同 它用的是“写时复制”:每次
add()
或
remove()
都新建数组,而迭代器始终持有一个创建时的数组快照。所以遍历时绝对安全,永远不抛
ConcurrentModificationException
,但也意味着你永远看不到遍历开始后发生的任何修改。 代价很直接:内存占用翻倍(旧数组暂不回收),且写操作延迟高(复制整个数组)。别在写多读少或元素很大的场景用它。 立即学习 “ Java免费学习笔记(深入) ”; Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 适合读远多于写的场景,比如监听器列表、配置白名单
iterator()
返回的
ListIterator
不支持
set()
和
add()
,调用会抛
UnsupportedOperationException
迭代器中的
hasNext()
和
next()
是线程安全的,但结果反映的是迭代开始那一刻的状态 哪些容器的迭代器会抛
ConcurrentModificationException
标准非并发集合:
ArrayList
、
HashMap
、
HashSet
、
LinkedList
等。它们的迭代器都检查
modCount
字段——只要结构修改次数和迭代器初始化时记录的不一致,下一次
next()
或
hasNext()
就抛异常。 这个异常不是同步机制,只是“快速失败”(fail-fast)策略,用来暴露错误逻辑,不是用来控制并发的。 单线程里也会触发:比如在
for-each
循环中直接调用
list.remove(x)
不能靠捕获它来实现并发控制;它不提供任何同步语义 子类重写
add()
但忘了更新
modCount
,也会导致误报或漏报 真要边遍历边修改,该选哪个容器 没有银弹。得看你要什么: 需要强一致性(看到全部最新变更)→ 改用显式同步:
synchronized(list) { ... }
,或
Collections.synchronizedList()
+ 手动加锁遍历 能接受“漏掉部分更新”+ 高并发读 →
ConcurrentHashMap
或
ConcurrentLinkedQueue
读极多、写极少、元素不多 →
CopyOnWriteArrayList
修改必须原子、且要精确控制可见性 → 别用迭代器,改用
computeIfAbsent()
、
replaceAll()
等内置原子方法 最容易被忽略的一点:弱一致性不是免费的。它把复杂性下沉到了内存模型和 JVM 指令重排层面——你看到的“没抛异常”,背后是大量
volatile
读、
Unsafe
CAS 和对 CPU 缓存行的精细控制。别把它当成“随便改”的许可证。

相关文章