60 秒回答模板

HashMap 和 ConcurrentHashMap 都是哈希桶结构,JDK 8 里冲突严重时会从链表树化为红黑树。HashMap 非线程安全,并发 put、扩容和读写交错可能出现数据丢失或结构异常;ConcurrentHashMap 面向并发访问,JDK 8 主要用数组元素的 volatile 可见性、CAS 初始化桶、桶级 synchronized 写入、协助扩容和计数分散来降低竞争。它的读通常不加全局锁,迭代是弱一致视图,并且不允许 null key 或 null value,因为并发下 get 返回 null 必须明确表示不存在。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

底层结构相似

两者核心都是数组加桶节点,哈希值定位数组下标,冲突时在桶内形成链表;当链表长度超过阈值且数组容量足够时,桶会树化为红黑树,以降低极端冲突下的查询复杂度。

02

HashMap 不保证并发安全

HashMap 的 put、resize 和结构修改没有同步保护,多线程同时写可能覆盖更新、读到中间状态或造成元素丢失。即使 JDK 8 避免了早期扩容环链问题,也不能把 HashMap 当并发容器。

03

ConcurrentHashMap 缩小锁粒度

JDK 8 ConcurrentHashMap 不再用固定 Segment 分段锁作为主要结构,而是基于 Node 数组、CAS、volatile 和桶级 synchronized。空桶用 CAS 放入首节点,非空桶写入时锁住桶头,读路径尽量无锁。

04

扩容和计数也面向并发

ConcurrentHashMap 扩容时多个线程可以协助迁移桶,通过 forwarding node 标识迁移状态;size 计数使用 baseCount 和 counterCells 分散更新,降低高并发下单点计数竞争。

05

语义差异影响使用

ConcurrentHashMap 的迭代器是弱一致的,遍历时可以容忍并发修改,但不保证看到某个瞬间的完整快照。它不允许 null key 或 null value,避免并发场景下把“key 不存在”和“value 为 null”混淆。

易错点

  • 只回答“一个线程安全、一个不安全”,没有解释结构和并发控制。
  • 把 JDK 7 的 Segment 分段锁原样套到 JDK 8。
  • 说 ConcurrentHashMap 绝对无锁,忽略桶级 synchronized 和扩容协作。
  • 不知道不允许 null 的原因,只把它当成 API 规定死记。

面试官追问

JDK 8 ConcurrentHashMap 还是分段锁吗?

不是传统 JDK 7 那种 Segment 数组分段锁。JDK 8 的主要结构是 Node 数组,写入时在桶级别加锁,并结合 CAS、volatile 和协助扩容。

为什么 ConcurrentHashMap 不允许 null?

并发环境下 `get(key)` 返回 null 需要明确表示 key 不存在。如果允许 value 为 null,调用方无法区分是没这个 key,还是 key 存在但值为 null,尤其在并发修改时更危险。

ConcurrentHashMap 的读一定不加锁吗?

普通 get 依赖 volatile 可见性和节点链路读取,通常不加锁;但遇到正在迁移或树节点等特殊结构会走相应逻辑。它不是完全无锁容器,写入仍可能锁桶。

HashMap 什么时候树化?

桶内链表长度达到树化阈值,并且数组容量达到最小树化容量时才会树化;容量太小时优先扩容,因为扩容后冲突可能自然分散。