ConcurrentHashMap1.8实现原理图深度解析

2025年Java面试宝典抢先领:
点击获取网盘资源
(提取码:9b3g)
一、ConcurrentHashMap1.8的核心变革
相比1.7的分段锁,ConcurrentHashMap1.8 改用 Node数组+链表/红黑树 结构,通过 CAS+synchronized 实现细粒度锁。最关键的改进是:
- 锁粒度从Segment级别缩小到桶级别(单个链表头节点)
- 引入红黑树解决哈希冲突导致的链表退化
- 使用volatile保证内存可见性
二、数据结构实现原理
+-----------+ +------+ +------+
数组索引 | 桶0 (Node) | ---> | Key1 | -> | Key2 | (链表)
+-----------+ +------+ +------+
| 桶1 (TreeBin)|
+-----------+ +------+ +------+
| ---> | TreeNode| -> | TreeNode| (红黑树)
+------+ +------+
- Node节点:存储键值对,含
volatile V val和volatile Node<K,V> next - TreeBin节点:当链表长度>8且数组长度≥64时,转换为红黑树容器
- ForwardingNode:扩容时的占位节点(标志当前桶已迁移)
三、put操作实现原理

-
计算桶位置
int index = (n - 1) & hash(n为数组长度) -
桶为空时CAS写入
若目标桶为null,直接用CAS创建新Node -
桶非空时同步锁处理
synchronized (f) { // f是链表头节点 if (链表结构) { 遍历链表更新或插入新节点 if (链表长度≥8) 尝试树化 } else if (红黑树结构) { 调用TreeBin.putTreeVal() } }
四、扩容实现原理(关键点!)

-
触发条件
- 元素总数 ≥ 扩容阈值(容量*0.75)
- 单个链表长度≥8但数组长度<64(优先扩容而非树化)
-
多线程协同迁移
- 每个线程领取16个桶的迁移任务
- 迁移完成的桶用ForwardingNode标记
- 读操作遇到ForwardingNode会协助迁移
-
扩容期间读写并发
- 读操作:若桶未迁移,走原链表/红黑树;若已迁移,转发到新数组
- 写操作:遇到ForwardingNode线程会加入迁移
五、读操作无锁优化
V get(Object key) {
Node<K,V>[] tab;
Node<K,V> e;
int h = spread(key.hashCode());
if ((tab = table) != null) {
if ((e = tabAt(tab, (n - 1) & h)) != null) {
// 遍历链表或红黑树(TreeBin.find())
}
}
return null;
}
- tabAt():通过
Unsafe.getObjectVolatile保证可见性 - 链表遍历:依赖
volatile Node.next - 红黑树查询:TreeBin维护读写锁,读操作无锁
六、高频面试题精解
Q:ConcurrentHashMap1.8为何放弃分段锁?
A:分段锁的并发度受Segment数量限制(默认16),而1.8的锁粒度是桶级别,理论上并发度=数组长度,且内存占用更低
Q:树化阈值为何是8?退树阈值是6?
A:基于泊松分布,链表长度=8的概率仅0.000006%,避免不必要的树化开销。退树阈值设6是为避免频繁转换
Q:size()方法如何保证准确性?
A:通过baseCount+CounterCell[]分片计数,最终结果 = baseCount + ∑CounterCell。但高并发下仍是近似值
面试鸭会员福利:通过 面试鸭返利网 找我购买会员可返利25元!海量ConcurrentHashMap1.8实现原理真题解析等你解锁!
返回首页 | 更多面试技巧


