ConcurrentHashMap 详解
一、基本概念
ConcurrentHashMap 是 Java 并发包中提供的线程安全版本的 HashMap,它通过精细化的锁机制(JDK1.7 分段锁,JDK1.8 CAS + synchronized)实现了高并发下的线程安全。
核心特性:
- 线程安全:支持多线程并发读写
- 高并发:比 Hashtable 和 Collections.synchronizedMap 性能更好
- 弱一致性:迭代器反映的是创建时的状态,不保证反映所有更新
- 不允许 null 键值:与 HashMap 不同,键值都不能为 null
二、JDK 1.7 实现(分段锁)
1. 数据结构
// 分段数组
final Segment<K,V>[] segments;
// 段(继承ReentrantLock)
static final class Segment<K,V> extends ReentrantLock implements Serializable {
// 每个Segment包含一个HashEntry数组
transient volatile HashEntry<K,V>[] table;
// ...
}
// 链表节点
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
// ...
}
2. 并发机制
- 分段锁:将数据分成多个 Segment(默认16个),每个 Segment 独立加锁
- 并发度:同时支持最多 N 个线程并发(N = Segment 数量)
3. 操作流程
-
定位 Segment:
Segment<K,V> segment = segments[hash & segments.length-1]; -
Segment 内部操作:
- 获取锁(
segment.lock()) - 执行类似 HashMap 的操作
- 释放锁
- 获取锁(
三、JDK 1.8 实现(CAS + synchronized)
1. 数据结构
// Node数组(类似HashMap)
transient volatile Node<K,V>[] table;
// 链表节点(与HashMap相同)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V value;
volatile Node<K,V> next;
// ...
}
// 红黑树节点
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
// ...
}
// 转发节点(扩容时使用)
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
// ...
}
2. 并发机制
- CAS(Compare And Swap):无锁算法用于初始化、计数等
- synchronized:只锁定单个桶(链表头节点/树根节点)
- volatile:保证内存可见性
3. 关键优化
- 锁粒度细化:从段级别细化到桶级别
- 无锁化设计:
- 使用 CAS 实现无锁初始化
- 使用
sun.misc.Unsafe直接操作内存
- 扩容协助:多线程可以协同扩容
四、核心操作详解
1. put 操作流程
- 计算 key 的 hash 值
- 如果表为空,初始化表(CAS)
- 定位到具体桶:
- 如果桶为空,CAS 插入新节点
- 如果桶不为空:
- 加锁(synchronized 桶头节点)
- 链表处理:遍历、更新或插入
- 树处理:调用树方法
- 检查是否需要转树(链表长度≥8)
- 检查是否需要扩容
2. get 操作流程
- 计算 hash 值
- 定位到桶
- 根据桶类型(链表/树)查找
- 无锁设计,保证读性能
3. size 操作
JDK1.8 使用 CounterCell 数组(类似 LongAdder):
- 基础计数器:
baseCount - 竞争时分散到
CounterCell[] - 最终结果 =
baseCount + ∑CounterCell[i]
五、并发控制机制
1. 初始化控制
// 使用sizeCtl控制初始化
private transient volatile int sizeCtl;
// 初始化逻辑
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 初始化表
} finally {
sizeCtl = sc;
}
}
2. 扩容机制
- 多线程协助:当前线程处理完自己的区间后,可以协助其他线程
- ForwardingNode:标记正在迁移的桶
- 步长控制:每个线程处理一个区间(stride)
3. 计数机制
采用类似 LongAdder 的分段计数:
// 基础计数器
private transient volatile long baseCount;
// 计数单元数组
private transient volatile CounterCell[] counterCells;
六、与 HashMap 对比
| 特性 | HashMap | ConcurrentHashMap |
|---|---|---|
| 线程安全 | 否 | 是 |
| 锁粒度 | 无锁 | 桶级别锁 |
| null支持 | 允许 | 不允许 |
| 迭代器 | fail-fast | 弱一致 |
| 性能 | 最高 | 高(读接近无锁) |
| Java版本 | 所有 | JDK1.5+ |
七、使用场景
- 高并发缓存:如商品库存缓存
- 共享数据存储:多线程共享配置
- 计数器:统计点击量等
八、代码示例
// 创建
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 安全插入
map.put("key", 1);
// 原子更新
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
// 批量操作
map.forEach(2, // 并行度
(k, v) -> System.out.println(k + "=" + v)
);
// 搜索
Integer result = map.search(2,
(k, v) -> v > 100 ? k : null
);
九、常见问题
1. 为什么不允许 null?
避免二义性:无法区分"不存在"和"值为null"
2. 为什么读不需要锁?
- 使用 volatile 保证可见性
- Node 的 value 和 next 都是 volatile
3. 如何保证线程安全?
- 写操作:CAS + synchronized
- 读操作:volatile 变量
- 扩容:协作式迁移
4. JDK1.7 vs JDK1.8
| 版本 | 锁粒度 | 数据结构 | 并发度 |
|---|---|---|---|
| 1.7 | 段锁 | 数组+链表 | 段数 |
| 1.8 | 桶锁 | 数组+链表+红黑树 | 桶数 |
ConcurrentHashMap 是 Java 并发编程中的重要工具,理解其实现原理有助于编写高性能的并发程序。
1092

被折叠的 条评论
为什么被折叠?



