Java并发容器:ConcurrentHashMap实现原理深度剖析
Java并发容器:ConcurrentHashMap实现原理深度剖析
引言
在Java并发编程的世界里,线程安全与性能往往是一对矛盾体。传统的Hashtable和Collections.synchronizedMap虽然保证了线程安全,但通过全表锁的方式牺牲了并发性能,在现代高并发场景下已鲜有问津。作为HashMap的线程安全版本,ConcurrentHashMap凭借其精妙的设计,成为了高并发场景下首选的数据结构。
本文将深入剖析ConcurrentHashMap的实现原理,重点对比JDK 1.7与JDK 1.8的核心差异,详细解读CAS机制与synchronized在其中的应用,并结合实战代码探讨其在实际项目中的最佳实践。
核心概念:从分段锁到CAS+synchronized
理解ConcurrentHashMap的关键在于理解其锁机制的演进。
JDK 1.7:分段锁时代
在JDK 1.7中,ConcurrentHashMap采用了Segment分段锁机制。它将数据分为多个Segment(默认16个),每个Segment继承自ReentrantLock,本质上是一个小的Hashtable。
- 优点:不同Segment之间的操作可以并发进行,理论上最大并发度等于Segment的个数。
- 缺点:并发粒度仍然较粗,且Segment数组大小在初始化后无法扩容,可能导致数据倾斜。
JDK 1.8:CAS + synchronized 时代
JDK 1.8摒弃了Segment分段锁的概念,转而采用了CAS(Compare And Swap)+ synchronized的方案,数据结构与HashMap趋于一致:数组 + 链表 + 红黑树。
- 锁粒度更细:锁不再是Segment,而是具体的桶节点,大大降低了锁竞争的概率。
- CAS无锁操作:在插入新节点到空桶时,使用CAS无锁操作,性能极高。
- synchronized优化:仅在发生哈希冲突(锁住链表头节点或树根节点)时使用synchronized。现代JVM对synchronized做了大量优化(如偏向锁、轻量级锁),性能已不逊色于ReentrantLock。
技术原理深度剖析
1. 核心数据结构
JDK 1.8中,ConcurrentHashMap的核心成员变量如下:
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
// Node数组,也就是桶数组
transient volatile Node<K,V>[] table;
// 扩容时的新数组
private transient volatile Node<K,V>[] nextTable;
// 控制标识符,用于控制table初始化和扩容
// -1: 正在初始化; -N: 有N-1个线程正在进行扩容; >0: 下次扩容的大小
private transient volatile int sizeCtl;
// ...
}
2. put操作的核心流程
put操作是并发控制最复杂的环节,其核心逻辑如下:
- 计算Hash:计算key的哈希值,并对其进行扰动处理(Spread),减少哈希冲突。
- 循环插入:
- 情况A:数组为空。调用
initTable初始化数组。 - 情况B:目标桶为空。使用CAS操作尝试将新节点放入该桶。如果成功则结束;如果失败(说明有其他线程抢先插入了),则自旋重试。
- 情况C:正在扩容。如果发现桶的哈希值为
MOVED(-1),说明当前Map正在扩容,当前线程会帮助进行扩容(多线程协同扩容机制)。 - 情况D:哈希冲突。使用synchronized锁住当前桶的头节点,遍历链表或红黑树进行更新或插入。
- 情况A:数组为空。调用
- 转红黑树:插入完成后,检查链表长度。如果达到阈值(8),则将链表转换为红黑树,提高查询效率。
- 统计数量:调用
addCount()增加元素计数,并检查是否需要扩容。
3. 关键源码解读
以下是putVal方法的简化版核心逻辑解读:
```java
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 空值检查,ConcurrentHashMap不允许null键/值
if (key == null || value == null) throw new NullPointerException();
// 计算hash
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable(); // 初始化table
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 【关键点1】:目标桶为空,使用CAS无锁插入
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
else if ((fh = f.hash) == MOVED)
// 【关键点2】:发现扩容标志,帮助扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 【关键点3】:哈希冲突,使用synchronized锁住头节点
synchronized (f) {
if (tabAt(tab, i) == f) {
// 链表处理逻辑...
// 红黑树处理逻辑...
}
}
// 检查是否需要树化
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 增加计数,检查扩容
addCount(1L, binCount);
return null

浙公网安备 33010602011771号