# 问题
14. HashMap 的 modCount 变量有什么作用?为什么 fail-fast 机制需要它?
# 标准答案
modCount
变量用于记录 HashMap
在 结构性修改(如 put()
、remove()
、扩容等操作)时的 修改次数,主要作用是 支持 fail-fast 机制。fail-fast 机制在遍历过程中检测 modCount
是否变化,如果变化了(意味着有其他线程或方法修改了 HashMap
),Iterator
立即抛出 ConcurrentModificationException
,防止并发修改导致数据不一致问题。
# 答案解析
# 1. modCount 的作用
modCount
是 HashMap
的一个成员变量,定义如下:
transient int modCount;
它的作用是 记录 HashMap 结构修改的次数,主要包括:
put(K key, V value)
:新增或覆盖 key-value 时,如果 导致结构变化(如新增键值对、扩容),则modCount++
。remove(Object key)
:删除 key-value 也会 修改结构,modCount++
。resize()
(扩容):HashMap
触发 容量扩展 时,也会修改modCount++
。
# 2. 为什么 fail-fast 机制需要 modCount?
fail-fast 机制用于检测 并发修改异常,防止 遍历过程中数据变化 导致的不可预测行为。
在 HashMap
中,Iterator
内部会 缓存创建时的 modCount 值,在 next()
或 remove()
时检查 当前 modCount 是否等于缓存值:
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 返回下一个元素
}
2
3
4
5
如果 modCount
发生变化,说明 HashMap
在遍历过程中被 其他线程或方法修改,迭代器会立即抛出 ConcurrentModificationException
,防止错误读取数据。例如:
Map<String, String> map = new HashMap<>();
map.put("A", "Apple");
map.put("B", "Banana");
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
map.put("C", "Cherry"); // 结构发生变化
iterator.next(); // 触发 fail-fast,抛出 ConcurrentModificationException
}
2
3
4
5
6
7
8
9
如果没有 modCount 机制,在遍历过程中修改 HashMap
,可能会导致:
- 遍历结果不完整或重复
next()
访问已经删除的元素,导致NullPointerException
- 无限循环,甚至 CPU 100% 占用(如 JDK 1.7 的
HashMap
在高并发下可能导致链表成环)
# 3. 为什么 fail-fast 不是线程安全机制?
fail-fast 只是一种 快速失败检测机制,并不能防止真正的 并发问题。在多线程环境下:
- 多个线程同时读写
HashMap
:可能抛出ConcurrentModificationException
,但不能保证线程安全。 - 不同线程修改
HashMap
:fail-fast 只能在 单个迭代器 里生效,不会同步其他线程的修改。
如果需要 线程安全的并发访问,应使用 ConcurrentHashMap
,它采用 分段锁(JDK 1.7)或 CAS+链表/红黑树(JDK 1.8),支持 安全的迭代和修改。
# 深入追问
🔹 为什么 modCount
不能直接用于线程安全控制?
🔹 ConcurrentHashMap
为什么不使用 modCount
进行 fail-fast?
🔹 如何优雅地遍历 HashMap
而不触发 fail-fast
?
# 相关面试题
• 如何避免 ConcurrentModificationException
?
• 为什么 HashMap
不能在并发环境下使用?
• modCount
在 ArrayList
、HashMap
等数据结构中作用是否相同?