寒假打卡11-1月24日
Java ConcurrentHashMap 详解——并发安全实现原理
在多线程环境中,HashMap
并不是线程安全的,多个线程同时访问和修改 HashMap
可能会导致数据不一致的问题。为了解决这个问题,Java 提供了线程安全的 ConcurrentHashMap
类。ConcurrentHashMap
通过分段锁(Segmented Locking)和高效的并发算法,确保在高并发场景下的线程安全和高性能。
ConcurrentHashMap 的基本结构
ConcurrentHashMap
基于哈希表实现,与 HashMap
相似,但采用了更复杂的数据结构和锁机制。它通过将数据分成多个段(Segment),每个段维护自己的锁,从而实现高效的并发访问。
ConcurrentHashMap 的重要字段
transient volatile Node<K, V>[] table; // 存储元素的数组
private transient volatile int sizeCtl; // 控制表的初始化和调整大小
private transient volatile long baseCount; // 基本计数变量,用于统计元素数量
private transient volatile int cellsBusy; // 控制 cells 的初始化和更新
private transient volatile CounterCell[] counterCells; // 计数单元数组,用于统计元素数量
put 方法解析
put
方法用于向 ConcurrentHashMap
中添加键值对。如果键已经存在,则更新其对应的值;如果键不存在,则插入新的键值对。
put 方法的主要步骤
- 计算哈希值:通过
spread(hash)
方法计算键的哈希值。 - 定位桶:通过
tabAt(tab, i)
方法定位桶的索引。 - 插入新节点:如果桶为空,则通过 CAS 操作插入新节点;如果桶不为空,则加锁并遍历链表或红黑树,检查键是否已经存在。
- 扩容检查:检查是否需要扩容,如果需要则进行扩容。
源码解析
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();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K, V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K, V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K, V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K, V>(hash, key, value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K, V> p;
binCount = 2;
if ((p = ((TreeBin<K, V>)f).putTreeVal(hash, key, value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
在上述代码中,putVal
方法首先检查并初始化 table
数组,然后根据哈希值定位桶的索引。如果桶为空,则通过 CAS 操作插入新节点;如果桶不为空,则加锁并遍历链表或红黑树,检查键是否已经存在。如果键存在,则更新其值;如果键不存在,则插入新节点。
get 方法解析
get
方法用于根据键从 ConcurrentHashMap
中获取对应的值。如果键存在,则返回对应的值;如果键不存在,则返回 null
。
get 方法的主要步骤
- 计算哈希值:通过
spread(hash)
方法计算键的哈希值。 - 定位桶:通过
tabAt(tab, i)
方法定位桶的索引。 - 遍历链表或红黑树:查找键对应的节点,并返回其值。
源码解析
public V get(Object key) {
Node<K, V>[] tab; Node<K, V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
在上述代码中,get
方法首先通过 spread(hash)
计算哈希值,然后通过 tabAt(tab, i)
方法定位桶的索引,并遍历链表或红黑树查找键对应的节点,最后返回节点的值。
扩容机制
当 ConcurrentHashMap
中的键值对数量超过阈值时,需要对 table
数组进行扩容。扩容的过程包括:
- 创建新数组:创建一个容量是原数组两倍的新数组。
- 重新散列:将原数组中的节点重新散列到新数组中。
源码解析
private final Node<K, V>[] initTable() {
Node<K, V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K, V>[] nt = (Node<K, V>[])new Node<?, ?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
在上述代码中,initTable
方法用于初始化 table
数组,并设置阈值 sizeCtl
。在扩容过程中,ConcurrentHashMap
采用分段锁机制,确保多个线程可以并发进行扩容操作。
总结
通过解析 ConcurrentHashMap
的 put
和 get
方法源码,我们可以深入理解其底层逻辑和实现原理。ConcurrentHashMap
通过分段锁和高效的并发算法,确保在高并发场景下的线程安全和高性能。它是多线程环境下处理哈希表的理想选择。
希望通过本篇文章,大家对 Java ConcurrentHashMap
的底层实现有了更深入的了解。在接下来的文章中,我们将继续探讨更多关于 Java 并发编程的知识点,敬请期待!