# 问题

14. HashMap 的 modCount 变量有什么作用?为什么 fail-fast 机制需要它?

# 标准答案

modCount 变量用于记录 HashMap结构性修改(如 put()remove()、扩容等操作)时的 修改次数,主要作用是 支持 fail-fast 机制。fail-fast 机制在遍历过程中检测 modCount 是否变化,如果变化了(意味着有其他线程或方法修改了 HashMap),Iterator 立即抛出 ConcurrentModificationException,防止并发修改导致数据不一致问题。

# 答案解析

# 1. modCount 的作用

modCountHashMap 的一个成员变量,定义如下:

transient int modCount;
1

它的作用是 记录 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();
    // 返回下一个元素
}
1
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
}
1
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 不能在并发环境下使用?
modCountArrayListHashMap 等数据结构中作用是否相同?