JDK1.8的ConcurrentHashMap的put方法源码
一、JDK1.8的ConcurrentHashMap的put方法源码
ConcurrentHashMap 是 Java 并发包(java.util.concurrent)中的一个高性能线程安全哈希表实现。在 JDK 1.8 中,ConcurrentHashMap 的 put 方法是其核心方法之一,负责插入键值对并保证线程安全。
以下是 put 方法的详细源码解析,结合并发机制和设计思想
1、put 方法入口

-
参数:
-
key:键。
-
value:值。
-
onlyIfAbsent:如果为 true,则仅在键不存在时插入
-
-
返回值:如果键已存在,返回旧值;否则返回 null
2、putVal 方法解析
putVal 是 put 方法的核心实现,以下是其源码逐段解析:
(1) 参数校验

- 设计意图:ConcurrentHashMap 不允许 null 键或值,因为在并发场景中,null 可能导致歧义(例如,无法区分“键不存在”和“键对应的值为 null”)
(2) 哈希计算

- spread() 方法:对原始哈希码进行二次处理,确保哈希值均匀分布

-
作用:
-
通过异或高位和低位,减少哈希冲突
-
通过 & HASH_BITS 确保哈希值为正数(负哈希用于标记特殊节点,如扩容时的 ForwardingNode)
-
(3) 自旋插入逻辑

-
关键点:
-
CAS 无锁插入:若桶为空,直接通过 casTabAt 原子操作插入新节点,避免加锁
-
协助扩容:若发现桶已被标记为 MOVED(ForwardingNode),当前线程会协助迁移数据,提升扩容效率
-
(4) initTable() 初始化哈希表
initTable() 是 ConcurrentHashMap 在 JDK 1.8 中用于初始化哈希表的核心方法。当 ConcurrentHashMap 第一次插入数据时,如果哈希表尚未初始化,
会调用 initTable() 方法创建并初始化哈希表。以下是 initTable() 方法的详细源码解析。
a、方法签名

b、方法解析
i、检查表是否已初始化

-
条件:如果 table 为 null 或长度为 0,表示哈希表尚未初始化。
-
作用:确保哈希表未初始化时才执行初始化逻辑。
ii、检查是否有其他线程正在初始化

-
sizeCtl 的作用:
-
sizeCtl 是 ConcurrentHashMap 的控制状态变量,用于控制初始化和扩容
-
当 sizeCtl < 0 时,表示有其他线程正在初始化或扩容
-
-
Thread.yield():让出 CPU,等待其他线程完成初始化
iii、尝试 CAS 设置 sizeCtl 为 -1

-
CAS 操作:通过 CAS 将 sizeCtl 设置为 -1,表示当前线程正在初始化
-
作用:确保只有一个线程执行初始化逻辑
iV、双重检查

- 作用:防止其他线程已经完成初始化
iiV、计算初始容量

-
sc 的作用:
-
如果 sc > 0,表示用户指定了初始容量
-
否则,使用默认容量 DEFAULT_CAPACITY(默认值为 16)
-
-
作用:确定哈希表的初始容量
iiiV、创建哈希表

- 作用:创建长度为 n 的 Node 数组,并将其赋值给 table
iVi、计算扩容阈值

-
计算逻辑:
-
n >>> 2 表示 n / 4
-
n - (n >>> 2) 表示 n * 0.75
-
-
作用:设置扩容阈值(sizeCtl),当元素数量超过阈值时触发扩容
iVii、 恢复 sizeCtl 的值

- 作用:初始化完成后,恢复 sizeCtl 的值,表示初始化完成
c、示例场景
假设 ConcurrentHashMap 第一次插入数据:
1、线程 A 检查 table,发现尚未初始化
2、线程 A 通过 CAS 将 sizeCtl 设置为 -1,表示正在初始化
3、线程 A 创建哈希表,并设置扩容阈值
4、线程 A 恢复 sizeCtl 的值,表示初始化完成
5、其他线程在初始化期间会调用 Thread.yield(),等待初始化完成
(5) helpTransfer(tab, f)方法源码
helpTransfer(tab, f) 是 ConcurrentHashMap 在 JDK 1.8 中用于协助扩容的核心方法之一。当线程在插入数据时发现当前桶正在迁移(即桶的头节点是 ForwardingNode),会调用 helpTransfer 方法协助其他线程完成数据迁移。以下是 helpTransfer 方法的详细源码解析:
a、方法签名

b、方法解析
i、 检查扩容状态

-
条件:
-
1、当前表 tab 不为空。
-
2、当前桶的头节点 f 是 ForwardingNode(表示桶正在迁移)。
-
3、ForwardingNode 的 nextTable 不为空(表示扩容正在进行)。
-
-
作用:确认当前表正在扩容,且需要协助。
ii、 计算扩容戳

- resizeStamp() 方法:生成一个扩容戳,用于标识当前扩容的状态。

-
作用:
-
1、Integer.numberOfLeadingZeros(n):计算表长度的前导零个数
-
2、(1 << (RESIZE_STAMP_BITS - 1)):生成一个标志位
-
3、最终结果是一个唯一的扩容戳,用于标识当前扩容
-
iii、 检查扩容是否仍在进行

-
条件:
-
1、nextTab 仍然是 nextTable(扩容目标表未改变)。
-
2、table 仍然是 tab(当前表未改变)。
-
3、sizeCtl 为负数(表示扩容仍在进行)。
-
-
作用:确保当前扩容状态未发生变化
vi、判断是否需要协助

-
条件:
-
1、(sc >>> RESIZE_STAMP_SHIFT) != rs:扩容戳不匹配。
-
2、sc == rs + 1:扩容已完成。
-
3、sc == rs + MAX_RESIZERS:扩容线程数已达到最大值。
-
4、transferIndex <= 0:所有桶已分配完毕。
-
-
作用:如果满足任一条件,则无需协助,直接退出。
vii、尝试增加扩容线程数

-
CAS 操作:通过 CAS 将 sizeCtl 的值加 1,表示当前线程加入扩容
-
transfer() 方法:调用 transfer 方法协助迁移数据
viii、返回新表

- 作用:返回扩容目标表 nextTab,供后续操作使用
c、transfer 方法的作用
transfer 方法是 ConcurrentHashMap 扩容的核心逻辑,负责将数据从旧表迁移到新表。以下是 transfer 方法的主要步骤:
-
1、计算迁移范围:
-
每个线程负责迁移一部分桶
-
通过 transferIndex 分配迁移任务
-
-
2、迁移数据:
-
遍历旧表中的每个桶,将节点迁移到新表
-
如果桶是链表,则拆分为高低链表;如果是红黑树,则拆分为高低树
-
-
3、标记迁移完成:
- 迁移完成后,将桶标记为 ForwardingNode
d、示例场景
假设 ConcurrentHashMap 正在扩容,线程 A 在插入数据时发现当前桶正在迁移,调用 helpTransfer 方法协助迁移:
1、线程 A 检查扩容状态,确认需要协助
2、线程 A 通过 CAS 增加扩容线程数,并调用 transfer 方法迁移数据
3、线程 A 完成迁移任务后,继续执行插入操作
(6) transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) 解析
transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) 是 ConcurrentHashMap 在 JDK 1.8 中用于扩容和数据迁移的核心方法。当哈希表中的元素数量超过阈值时,ConcurrentHashMap 会触发扩容,
并通过 transfer 方法将数据从旧表迁移到新表。transfer 方法支持多线程协作迁移数据,以提高扩容效率。
以下是 transfer 方法的详细源码解析
a、方法签名
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// 1. 计算每个线程负责的迁移范围(stride)
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // 最小迁移范围
// 2. 初始化新表(nextTab)
if (nextTab == null) {
try {
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 新表容量为旧表的两倍
nextTab = nt;
} catch (Throwable ex) {
sizeCtl = Integer.MAX_VALUE; // 扩容失败,恢复 sizeCtl
return;
}
nextTable = nextTab;
transferIndex = n; // 初始化迁移索引
}
int nextn = nextTab.length;
// 3. 创建 ForwardingNode 节点,用于标记迁移中的桶
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
boolean finishing = false; // 是否完成迁移
// 4. 自旋迁移数据
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// 4.1 分配迁移任务
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// 4.2 检查迁移是否完成
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1); // 更新 sizeCtl
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // 重新检查所有桶
}
}
// 4.3 迁移空桶
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
// 4.4 迁移已处理的桶
else if ((fh = f.hash) == MOVED)
advance = true; // 跳过已迁移的桶
// 4.5 迁移非空桶
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) { // 链表节点
// 拆分为高低链表
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
else if (f instanceof TreeBin) { // 红黑树节点
// 拆分为高低树
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
b、方法解析
i、计算迁移范围

-
stride:每个线程负责迁移的桶数量
-
NCPU:CPU 核心数
-
MIN_TRANSFER_STRIDE:最小迁移范围(默认 16)
-
作用:根据 CPU 核心数动态分配迁移任务
ii、初始化新表

- 作用:创建新表,容量为旧表的两倍
iii、创建 ForwardingNode

- ForwardingNode:用于标记迁移中的桶,其他线程遇到时会协助迁移
Vi、自旋迁移数据

-
关键点:
-
分配迁移任务:通过 CAS 更新 transferIndex,分配迁移范围
-
迁移空桶:将空桶标记为 ForwardingNode
-
迁移非空桶:加锁迁移链表或红黑树
-
Vii、迁移链表

- 作用:将链表拆分为高低链表,分别迁移到新表
Viii、迁移红黑树

- 作用:将红黑树拆分为高低树,分别迁移到新表
c、总结
transfer 方法:用于扩容和数据迁移,支持多线程协作。
-
核心机制:
-
通过 CAS 分配迁移任务。
-
使用 ForwardingNode 标记迁移中的桶。
-
渐进式迁移,不影响读操作。
-
-
适用场景:ConcurrentHashMap 的动态扩容
(7) 处理哈希冲突(加锁逻辑)

-
锁策略:
-
细粒度锁:仅锁住当前冲突的桶(头节点),其他桶仍可并发访问。
-
双重检查:加锁后再次检查桶是否被修改,避免锁期间其他线程已修改结构
-
-
链表与红黑树:
-
链表插入采用尾插法(JDK 1.8 的改进,避免循环链表问题)。
-
当链表长度超过 TREEIFY_THRESHOLD(默认8),会转换为红黑树。
-
(8) 链表转红黑树

-
触发条件:单个桶的链表长度 ≥ TREEIFY_THRESHOLD(默认8)。
-
实际转换条件:
- 哈希表长度 ≥ MIN_TREEIFY_CAPACITY(默认64),否则优先扩容。
(9) 更新元素计数

- addCount() 方法:使用类似 LongAdder 的分段计数机制,减少 CAS 竞争。

- 设计目的:通过分散计数到多个 CounterCell,避免所有线程竞争同一变量
二、核心并发设计总结
1、无锁读:读操作直接访问 volatile 变量,无需同步。
2、CAS 写:桶为空时通过 CAS 插入,避免锁竞争。
3、细粒度锁:哈希冲突时仅锁住当前桶,锁粒度极小。
4、分段计数:使用 CounterCell 分散计数,减少 CAS 冲突。
5、协作扩容:多线程协同迁移数据,提升扩容效率。

浙公网安备 33010602011771号