ConcurrentHashMap
是 Java 中线程安全的哈希表实现,专为高并发场景设计。它在保留 HashMap
高效查询特性的同时,通过分段锁、CAS 操作和细粒度同步等机制实现线程安全。以下是其核心设计原理和关键特性:
1. 线程安全实现(Java 8 及之后)
a. 分段锁 → 节点级锁
- Java 7:采用 分段锁(Segment),将整个哈希表划分为多个段(Segment),每个段独立加锁,降低锁竞争。
- Java 8 及之后:优化为 节点级锁(锁住单个桶的头节点),进一步减少锁粒度,提升并发度。
b. 关键机制
- CAS(Compare-And-Swap):用于初始化、空桶插入等无竞争场景,避免加锁。
- synchronized:对非空桶的头节点加锁,保证写操作线程安全。
- volatile:桶数组和节点中的值用
volatile
修饰,保证可见性。
2. 核心操作流程(以 put 为例)
- 计算哈希值:与
HashMap
类似,使用扰动函数生成哈希值。 - 定位桶位置:
(n - 1) & hash
确定键值对所在的桶。 - 插入或更新:
- 空桶:CAS 尝试直接插入新节点,无需锁。
- 非空桶:对头节点加
synchronized
锁,遍历链表或红黑树插入/更新。
- 扩容支持:多线程协同扩容,当前线程插入时若发现正在扩容,会协助迁移数据。
3. 关键特性
a. 并发度与性能
- 高并发读:读操作完全无锁(依赖
volatile
可见性)。 - 高并发写:不同桶的写操作互不影响,仅相同桶的写操作会竞争同一把锁。
b. 扩容机制
- 多线程协助扩容:当某个线程触发扩容(元素数 ≥ 容量 × 负载因子),其他线程在插入时也会协助迁移数据。
- 增量迁移:旧数组分块迁移,避免长时间阻塞。
c. 统计大小(size())
- 分段计数:通过
CounterCell[]
分散统计,减少竞争。 - 最终一致性:返回的 size 是近似值(并发环境下精确计数成本高)。
4. 与 HashMap、Hashtable 的对比
ConcurrentHashMap | HashMap | Hashtable | |
---|---|---|---|
线程安全 | 是(分段锁/CAS+节点锁) | 否 | 是(全局锁,性能低) |
锁粒度 | 节点级锁(Java 8) | 无锁 | 全局锁(整个表加锁) |
并发度 | 高 | 无 | 低 |
Null 键/值 | 不允许 | 允许一个 null 键/值 | 不允许 |
迭代器 | 弱一致性(安全但可能过时) | 快速失败(Fail-Fast) | 快速失败(Fail-Fast) |
5. 使用场景
- 高并发读写:如缓存、实时计数器等。
- 替代 Hashtable:需要线程安全且更高性能的场景。
- 避免锁竞争:适合写操作分散在不同桶的场景。
6. 注意事项
- 非原子组合操作:例如
computeIfAbsent
是原子的,但多个独立操作(如先检查后插入)仍需外部同步。 - 批量操作:
forEach
、search
等方法支持并行处理,但结果依赖并发状态。 - 初始容量与并发级别:根据预估并发数设置合理的初始容量,避免频繁扩容。
源码简析(Java 8)
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode()); // 计算哈希值
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable(); // 初始化表(CAS)
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break; // CAS 插入空桶成功
} else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); // 协助扩容
else {
synchronized (f) { // 锁住头节点
// 遍历链表或红黑树插入/更新
if (tabAt(tab, i) == f) {
if (fh >= 0) {
// 处理链表
} else if (f instanceof TreeBin) {
// 处理红黑树
}
}
}
}
}
addCount(1L, binCount); // 更新计数,可能触发扩容
return null;
}
总结
ConcurrentHashMap
通过 CAS + synchronized + volatile 的组合策略,在保证线程安全的同时,最大化并发性能。理解其设计哲学和关键机制,有助于在高并发场景中合理使用并优化性能。
(望各位潘安、各位子健/各位彦祖、于晏不吝赐教!多多指正!🙏)