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