# Java ConcurrentHashMap 原理`ConcurrentHashMap` 是 Java 并发包(`java.util.concurrent`)提供的线程安全 **哈希表**,相比 `Hashtable` 和同步 `HashMap`,它具有 **更高的并发性能**。 不同 JDK 版本实现略有不同:- JDK 7:分段锁(Segment + ReentrantLock)- JDK 8:CAS + synchronized + 链表 / 红黑树,无段锁---# 一、JDK 7 原理(分段锁 SegmentedLock)1. 数据结构ConcurrentHashMap
├─ Segment[] segments
├─ HashEntry[]
- `Segment` 是哈希表的一个小段,内部继承 `ReentrantLock`- 每个 `Segment` 包含一个哈希桶数组 `HashEntry[]`- 每个哈希桶是链表,存储 `key-value`2. 并发策略- 不同 `Segment` 之间可以 **并发访问**- 每个 `Segment` 内使用锁保证线程安全- 读操作(get)通常 **不加锁**,因为 volatile 保证可见性3. 访问流程```textget(key): - 计算 hash - 定位 Segment - 遍历桶链表查找 keyput(key, value): - 计算 hash - 定位 Segment - 上 Segment 锁 - 插入 / 更新链表 - 解锁- 并发性能
- 默认 16 段(Segment)
- 不同段之间操作互不影响
- 最大并发数 = Segment 数量
二、JDK 8 原理(无段锁,CAS + synchronized)
- 数据结构
ConcurrentHashMap ├─ Node<K,V>[] table ├─ Node<K,V> 链表 / 树(红黑树)Segment被移除- 使用 数组 + 链表 / 红黑树 存储
- Node 对象包含 key、value、hash、next
- 并发策略
- 读操作无需锁,使用 volatile 保证可见性
- 写操作:
- 链表为空 → 使用 CAS 插入
- 链表非空 → synchronized 加锁单个桶(链表头)插入
- 当链表过长(> 8) → 转换为红黑树,降低查找复杂度 O(n) → O(log n)
- 访问流程
get(key): - 计算 hash - 定位桶 index = hash & (table.length - 1) - 遍历链表 / 红黑树put(key, value): - 计算 hash - 定位桶 - 使用 CAS 尝试插入 - 如果失败: - synchronized 当前桶头,插入 / 更新 - 如果链表过长 → 转红黑树- 扩容(resize)
- JDK 8 ConcurrentHashMap 支持 分段迁移,不一次性全表锁
- 扩容过程可以被多个线程协作完成
- 保证 读写不阻塞
三、关键特性
- 高并发读写
- JDK 7:分段锁
- JDK 8:CAS + synchronized + 链表/红黑树
- 非阻塞读取
- get 不加锁,读性能高
- 支持并发扩容
- JDK 8 扩容无全表阻塞
- 链表 → 红黑树
- 当桶内元素过多,性能从 O(n) → O(log n)
- 线程安全
- 保证 原子操作(putIfAbsent, remove, replace)
四、面试常问点
- JDK 7 vs JDK 8 的区别?
- 7:Segment 分段锁,锁粒度较粗
- 8:无 Segment,CAS + synchronized + 链表/树
- 如何保证读操作线程安全?
- Node.value 使用 volatile
- get 不加锁
- put 操作线程安全如何实现?
- CAS + synchronized 锁桶头
- 保证链表插入/更新原子性
- 扩容是如何并发进行的?
- 使用 forwarding Node + 分段迁移
- 多线程协作完成桶迁移
- 链表过长怎么优化?
- 转红黑树
五、总结
- JDK 7:Segment + ReentrantLock
- JDK 8:CAS + synchronized + 链表/红黑树
- 高并发场景读性能高,写性能通过局部锁保证
- 扩容支持并发,保证系统可用性
核心理念:
减少锁粒度非阻塞读局部锁写链表转红黑树面试高分回答:
“ConcurrentHashMap 在 JDK 8 中采用无段锁设计,读操作不加锁,写操作对单个桶同步,使用 CAS 提升并发,链表过长时转红黑树,同时扩容可以并发进行。相比 JDK 7 的分段锁,实现了更高的并发性能和扩展性。”


