ConcurrentHashMap原理#
一句话答案#
JDK8 用 CAS + synchronized 锁单个 Node 实现并发安全,数组+链表+红黑树结构,锁粒度细化到桶级别,不再受固定 Segment 数限制。
核心要点
JDK 7:Segment 分段锁
ConcurrentHashMap
├── Segment[0](继承 ReentrantLock)
│ └── HashEntry[] table(小 HashMap)
├── Segment[1](继承 ReentrantLock)
│ └── HashEntry[] table
...
└── Segment[15](默认 16 个 Segment)plaintext- 将整个 Map 分为 N 个 Segment(默认16),每个 Segment 独立加锁
- 不同 Segment 的操作可以并发,同一 Segment 串行
- 并发度 = Segment 数量 = 16
JDK 8:CAS + synchronized(桶级别细粒度锁)
底层结构与 HashMap 一致:Node[] table + 链表/红黑树
关键变化:
- 锁粒度从 Segment 细化到单个桶(数组槽)
- 初始化:CAS 保证 table 只被初始化一次
- 空桶插入:CAS 无锁插入
- 非空桶操作:synchronized 锁住桶的头节点plaintextJDK 8 的改进:
| 维度 | JDK 7 | JDK 8 |
|---|---|---|
| 锁机制 | ReentrantLock(Segment级) | synchronized(桶级)+ CAS |
| 锁粒度 | 一个 Segment(多个桶) | 单个桶(最细粒度) |
| 并发度 | 固定(Segment数量,默认16) | 锁粒度为单个桶,不再有固定上限(实际受热点桶冲突、扩容等影响) |
| 数据结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
| size() | 先不加锁尝试,失败再全Segment加锁 | CounterCell(LongAdder思想) |
JDK 8 的 CAS 操作:
// 空桶插入:无锁
if (casTabAt(tab, i, null, new Node<>(hash, key, value)))
break;
// 非空桶插入:加锁(锁桶头节点)
synchronized (f) { // f = table[i]
// 在链表/红黑树中操作
}java为什么 JDK 8 用 synchronized 而不是 ReentrantLock:
JDK 6 后 synchronized 经过大量优化(偏向锁/轻量级锁/锁升级),在竞争不激烈时性能与 ReentrantLock 相当,且 JVM 能更好地优化 synchronized(如锁消除、锁粗化)。
面试回答(2分钟版)
ConcurrentHashMap是线程安全的HashMap,JDK7和JDK8的实现方案有本质区别。JDK7采用Segment分段锁机制,将整个Map分成默认16个Segment,每个Segment继承ReentrantLock相当于一个独立的小HashMap,不同Segment可以并发操作,并发度等于Segment数量即16。JDK8做了彻底重构,底层结构与HashMap一致采用数组加链表加红黑树,锁粒度从Segment级别细化到单个桶。具体策略是:对空桶的插入操作使用CAS无锁方式完成,对非空桶的操作则用synchronized锁住桶的头节点Node,这样锁粒度细化到桶级别,不再受固定Segment数量限制,比JDK7提升了一个数量级。JDK8选择synchronized而非ReentrantLock是因为JDK6之后synchronized经过偏向锁、轻量级锁、锁升级等大量优化,在竞争不激烈时性能已经和ReentrantLock持平,且JVM能对synchronized做锁消除和锁粗化等更深层优化。size()方法也做了改进,使用baseCount加CounterCell数组的方式统计,借鉴了LongAdder的分散热点思想,避免了JDK7中多次尝试后全段加锁的开销。
追问与易错
追问方向:
- “JDK7 和 JDK8 的实现有什么区别?”→ JDK7 用 Segment 分段锁(继承 ReentrantLock),并发度固定 16;JDK8 改为 CAS + synchronized 锁单个桶头节点,锁粒度细化到桶级别
- “size() 是怎么统计的?”→ 使用 baseCount + CounterCell 数组,借鉴 LongAdder 分散热点思想,CAS 更新 baseCount 失败时分散到 CounterCell 数组,最终 sum 求和
- “能保证复合操作的原子性吗?”→ 不能,单个方法如 putIfAbsent/computeIfAbsent 是原子的,但多个方法组合(如先 get 再 put)不是原子的,需要自行加锁或使用 compute 系列方法
易错点:
- ❌ “JDK8 的 ConcurrentHashMap 不用锁了”——synchronized 锁的是链表头节点
- ❌ 认为 ConcurrentHashMap 的迭代器是强一致的——是弱一致性