ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
ConcurrentHashMap 是 Java 中用于高并发环境下线程安全的哈希表,它的底层实现经历了几个阶段的演进,尤其从 JDK 1.7 到 JDK 1.8,有了较大的结构性变化。
一、核心结论先行
| 特性 | Java 7 实现 | Java 8 实现 |
|---|---|---|
| 数据结构 | 分段锁(Segment + HashEntry) | 数组 + 链表 + 红黑树(Node[] + TreeNode) |
| 并发控制 | Segment 级别的锁 | CAS + synchronized + 自旋 |
| 锁粒度 | 默认 16 个 Segment,锁粒度较粗 | 节点级锁,粒度更细 |
| 性能 | 中等(锁冲突较多) | 更高(极低冲突场景几乎无锁) |
| 是否支持 null key/value | ❌ 不支持 | ❌ 不支持 |
二、JDK 1.8 中的底层结构(重点)
主结构:Node<K,V>[] table
transient volatile Node<K,V>[] table;
- 整体类似于
HashMap,但数组元素是Node对象(非 Entry) - 每个桶(bucket)可能是:
- 空
- 链表(Node -> Node -> …)
- 红黑树(TreeNode)
Node 结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
注意:
val和next是 volatile 的,确保可见性;但结构更新使用的是 CAS(compareAndSwap)
三、并发控制机制详解(JDK 1.8)
1、读操作(get)
- 采用无锁操作(只读 volatile 字段)
- 遍历链表或红黑树查找值,完全不加锁 → 高性能
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e; int n; K k;
// ... 计算 hash、定位桶位 ...
// 遍历链表 / 红黑树获取 key 对应的值
}
2、写操作(put)
- 先通过
CAS(compare-and-swap)尝试插入空桶 → 无需加锁 - 如果 CAS 失败(冲突),则进入
synchronized块锁住该桶头节点 - 插入时:
- 桶中是链表:插入尾部,如果链表过长 → 转为红黑树
- 桶中是红黑树:执行红黑树插入算法(如平衡、旋转)
synchronized (f) {
// 桶头 f 非 null,进行链表/树更新
}
3、扩容操作(resize)
- 与
HashMap类似,但使用多线程协作扩容 - 利用**
ForwardingNode占位符**标记旧桶已转移 - 使用一个
transferIndex表示下一个迁移的桶段位置,每个线程搬迁一部分桶位
// 多线程扩容时,每个线程处理一部分桶
if ((f instanceof ForwardingNode)) {
// 帮助扩容
}
4、TreeNode 结构(红黑树)
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left, right;
boolean red;
// 红黑树插入、查找、旋转...
}
- 如果某桶链表长度 > 8 且桶数组容量 >= 64,就转换成红黑树
- 否则仍用链表(节省空间)
四、写时的并发安全如何实现?
| 操作 | 实现方式 |
|---|---|
| put() | 先尝试 CAS,失败后锁住桶头(synchronized) |
| remove() | 类似 put,先定位,再加锁删除 |
| resize() | 多线程并发迁移,使用 ForwardingNode 协作 |
- 它并不使用全表锁,也不使用传统锁表段(Segment),锁粒度非常小
- 借助 volatile 保证内存可见性,CAS 保证原子性
五、对比:HashMap vs ConcurrentHashMap
| 特性 | HashMap | ConcurrentHashMap |
|---|---|---|
| 线程安全性 | ❌ 非线程安全 | ✅ 线程安全 |
| 并发读写 | ❌ 可能抛异常 | ✅ 安全 |
| null 键/值支持 | ✅ 支持 | ❌ 都不支持 |
| 用于并发场景 | ❌ 不建议 | ✅ 推荐 |
六、源码入口 & 官方文档
七、面试必会重点总结
| 问题 | 回答概要 |
|---|---|
| 是否支持并发读写? | ✅ 是,并发读无锁,写锁粒度小 |
| 是否支持 null? | ❌ 不支持 key 或 value 为 null |
| 为什么写操作加锁? | 保证并发安全,避免链表/树结构竞争 |
| 扩容是否会阻塞? | ❌ 不会,采用多线程协作迁移 |
| 底层结构是什么? | Node 数组 + 链表 + 红黑树 |
| 如何保证线程安全? | CAS + volatile + synchronized |
更多详细内容请关注其他相关文章!