第23题:ConcurrentHashMap的底层原理是什么
📚回答:
JDK1.7 版本:
- 底层结构:基于分段锁(Segment)+ 链表实现。
- 核心原理:
ConcurrentHashMap将整个数组分为多个段(Segment),每个段是一个独立的ReentrantLock控制的哈希表。- 每个
Segment内部通过链表存储冲突的元素,锁粒度为Segment级别(默认16个段)。 - 不同线程可以同时操作不同的
Segment,从而提高了并发性能。
- 初始化过程:
- 调用无参构造时,默认创建长度为16的
Segment数组(即16个分段锁)。 - 插入元素时,先根据
key计算目标Segment的位置下标。如果该位置为空,则通过CAS(Compare-And-Swap)方式创建并插入一个Segment对象。 - 插入具体元素时,会对目标
Segment加锁,确保线程安全。
- 调用无参构造时,默认创建长度为16的
JDK1.8 版本:
底层结构:基于数组 + 链表 + 红黑树实现,去掉了分段锁设计,改用CAS + synchronized细化锁粒度。
核心原理:
- 调用无参构造时,不会立即初始化数组。首次调用
put方法时才会创建长度为16的数组。 - 插入元素时:
- 如果目标位置为空,使用CAS方式插入新节点。
- 如果发生哈希冲突,链表节点会以链表或红黑树的形式存储。
- 当链表长度超过阈值(默认8)时,自动转换为红黑树,提升查询效率。
- 锁粒度细化到头节点:在链表或红黑树插入时,使用
synchronized对链表头节点加锁,进一步提高并发性能。
💡代码示例:
以下伪代码展示了JDK1.8中put方法的核心逻辑:- 调用无参构造时,不会立即初始化数组。首次调用
if(table==null||table.length==0){// CAS初始化数组(非构造函数)initTable();}intindex=(n-1)&hash;Node<K,V>f=tabAt(tab,index);if(f==null){// CAS尝试插入新节点if(casTabAt(tab,index,null,newNode)){break;}}else{// 锁住头节点处理冲突synchronized(f){if(tabAt(tab,index)==f){// 双重检查if(f.hash>=0){// 链表节点// 遍历链表插入或树化}else{// 红黑树节点// 树操作逻辑}}}}💡面试官视角:
面试官可能会问“为什么JDK1.8去掉分段锁?”
答:因为分段锁虽然提升了并发性能,但锁粒度仍然较大。JDK1.8通过CAS和
synchronized锁细化到头节点,进一步提升了并发性能。面试官可能会追问“红黑树的引入有什么好处?”
答:当链表长度过长时,查询效率从O(n)提升到O(log n),显著优化了性能。
📌专栏:大白话说Java面试题 — 01-Java基础篇