ConcurrentHashMap中的put()方法是其中一个比较复杂的方法,其中涉及到了很多的知识点。比如,迁移时的多线程协助、桶位树化、多线程累加等等。下面我将详细解析put()方法和所涉及到的所有子方法的源码。
put()方法中其实是调用了putVal()来实现的。
/**
* Maps the specified key to the specified value in this table.
* Neither the key nor the value can be null.
*
* <p>The value can be retrieved by calling the {@code get} method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}
* @throws NullPointerException if the specified key or value is null
*/
public V put(K key, V value) {
return putVal(key, value, false);
}
putVal()方法会返回插入数据时发生冲突的值。整个方法大体步骤是:在多线程执行此方法时,如果遇到当前table正在扩容,则需要先协助扩容且扩容成功后再执行插入操作。插入成功后还需要判断当前桶位是否需要进行树化。最后,统计当前table元素个数,判断是否需要扩容。
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//控制key和value不能为空 hashmap允许是空
if (key == null || value == null) throw new NullPointerException();
//rehash
int hash = spread(key.hashCode());
//binCount 表示当前k-v封装成node后,插入到指定桶位后在链表中所属的下标
//0 表示当前要插入的桶位是null,node直接插入成为头结点
//>=1 表示当前桶位上node直接插入到链表的位置(位置可能不是表尾,如果插入时有冲突)
//2 表示当前要插入的桶位可能树化成红黑树
int binCount = 0;
//自旋
//tab 引用当前的table对象
Node<K,V>[] tab = table;
for (;;) {
//f: 表示桶位的头结点
//n: 表示散列表数组的长度
//i: 表示key通过寻址计算后,得到的桶位下标
//fh: 表示桶位头结点的hash值
Node<K,V> f; int n, i, fh;
//CASE1: true -> 表示当前table还未初始化
if (tab == null || (n = tab.length) == 0)
//初始化table
tab = initTable();
//前置条件: table已经初始化
//tabAt: 根据指定的table和地址,到主存中获取对应的值,如果有值,返回的是链表头结点
//CASE2: true -> 当前桶位为空,可以直接插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//true -> 将要插入的元素封装成node节点,然后插入到table下标为i并且值为null的桶位上
//false -> 进行下一次自旋
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
//插入成功,退出自旋
break; // no lock when adding to empty bin
}
//前置条件: 1、table已经初始化 2、当前桶位已经有节点了
//CASE3: true -> 表示当前头结点为 FWD 节点,说明目前table正在扩容中
else if ((fh = f.hash) == MOVED)
//表示当前线程要帮助其它线程进行迁移
tab = helpTransfer(tab, f);
//前置条件: 1、table已经初始化 2、当前桶位已经有节点了 3、当前table并没有在扩容
//CASE4: true -> 表示当前桶位可能是 链表 也可能是 treebin 红黑树代理节点
else {
//当key已存在,将旧值赋值给oldVal,并返回
V oldVal = null;
//获取对应桶位头元素f 的锁
synchronized (f) {
//这里也用了双重检验 避免有其它线程把这个桶位上的头结点改了,导致这个锁加错元素
if (tabAt(tab, i) == f) {
//true -> 说明当前桶位是普通链表节点
if (fh >= 0) {
//CASE1: 当前插入key与链表中所有的key都不一致时,当前的插入操作是追加到链表的末尾,binCount表示插入成功后链表长度
//CASE2: 当前插入key与链表中某个节点的key一致时,当前的插入操作是替换,binCount表示冲突的位置 binCount - 1
binCount = 1;
//循环遍历链表
//e: 表示当前循环的节点
for (Node<K,V> e = f;; ++binCount) {
//ek: 表示当前循环节点的key
K ek;
//条件1: true -> 链表中某个节点的hash与传入的hash匹配,进行下一步判断
//条件2: true -> 该链表节点的key与传入节点的key也匹配,进入方法体内
if (e.hash == hash &&
((ek = e.key) == key || (ek != null && key.equals(ek)))) {
//把匹配到的链表节点的value赋值给 oldVal
oldVal = e.val;
if (!onlyIfAbsent)
//value值替换操作
e.val = value;
//退出自旋
break;
}
//当前链表节点与传入的节点不匹配,走下面逻辑
//pred: 表示当前链表节点前一个节点
Node<K,V> pred = e;
//将e节点指针指向next元素,并判断是否为空
// true -> 表示当前链表节点已经是最后一个,下一步就是把传入的元素插入到末尾
// false -> 表示当前链表节点还不是最后一个,继续下一个循环
if ((e = e.next) == null) {
//将传入的节点插入到表尾
//此时的pred是指向当前链表节点
pred.next = new Node<K,V>(hash, key, value, null);
//退出自旋
break;
}
}
}
//前置条件: 该桶位不是链表
//true -> 说明当前桶位是红黑树代理节点
else if (f instanceof TreeBin) {
//p: 表示进入红黑树查找时,匹配到一个已存在的节点,会将这个节点返回
Node<K,V> p;
//binCount设置为2是因为 binCount <= 1 都已经有对应的含义了
binCount = 2;
//true -> 表示在插入到红黑树的过程中,匹配到一个已经存在的节点,将这个节点的引用返回给 p ,进行下一步操作
//false -> 表示直接插入成功,没有遇到冲突
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
//将旧值赋值给 oldVal
oldVal = p.val;
//true -> 不允许替换
//false -> 允许替换
if (!onlyIfAbsent)
//用新值替换旧值
p.val = value;
}
}
}
}
//下面的代码块的作用是 1、判断当前桶位是否需要树化 2、如果插入时有冲突,直接返回冲突值
//true -> 表示当前桶位不为null,可能是红黑树,可能是链表
if (binCount != 0) {
//true -> 如果binCount >= 8 表示当前桶位一定是链表,而且达到树化的条件(注意,这里并不一定会树化,还需要看整个table的元素个数)
if (binCount >= TREEIFY_THRESHOLD)
//树化操作
treeifyBin(tab, i);
//true -> 表示插入时发生了冲突,并将冲突节点的值返回
if (oldVal != null)
//返回冲突节点的值
return oldVal;
break;
}
}
}
//作用: 1、统计当前table一共有多少元素
// 2、判断是否达到扩容标准,触发扩容
addCount(1L, binCount);
return null;
}
initTable()方法是putVal()方法中初始化table时调用的。
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
//tab: 当前table的引用
//sc: 临时存放sizeCtl的局部变量
Node<K,V>[] tab; int sc;
//带条件的自旋 如果table没有初始化就一直自旋
while ((tab = table) == null || tab.length == 0) {
//sc大概率是-1 表示有线程正在进行创建table的过程,当前线程没有竞争到锁
if ((sc = sizeCtl) < 0)
//释放cpu资源
Thread.yield(); // lost initialization race; just spin
//true -> 表示获取锁成功,开始执行初始化逻辑
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//双重验证 防止有其它线程已经初始化完毕了,而当前线程再次初始化,所以需要双重判断一次
//true -> 说明还未有线程初始化当前map的table
if ((tab = table) == null || tab.length == 0) {
//sc > 0 true -> 初始化数组的大小 = 指定的大小sc
// false -> 初始化数组的大小 = 默认的大小 16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//创建一个以n为长度的node数组
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//将创建出来的数组 赋值给 map.table
table = tab = nt;
//n >>> 2 -> (1/4) * n
//n - (n >>> 2) -> n - (1/4) * n = (3/4) * n = 0.75 * n 表示扩容阈值
sc = n - (n >>> 2);
}
} finally {
//前置: 此时的 sizeCtl 在获取锁成功后是 = -1
//CASE1: 如果当前线程是初始化map.table的线程,sizeCtl = 下次扩容的阈值
//CASE2: 如果当前线程获取锁后发现table已经被其它线程初始化了,sizeCtl = 原来的值
sizeCtl = sc;
}
break;
}
}
return tab;
}
helpTransfer()方法是线程调用putVal()方法时,发现table正在扩容,此时,当前线程需要协助其它线程进行扩容操作。
/**
* Helps transfer if a resize is in progress.
*/
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
//nextTab: 迁移时的新table的引用
//sc: sizeCtl临时变量
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
//获取扩容标识戳
int rs = resizeStamp(tab.length);
//条件1: true -> 表示当前扩容还未完成
// false -> 表示当前扩容已完成 扩容后nextTable会被设置为null
//条件2: true -> 表示当前扩容还未完成
// false -> 表示当前扩容已完成 扩容后table会被赋值为nextTable的值
//条件3: true -> 表示当前扩容还未完成
// false -> 表示当前扩容已完成 扩容后sizeCtl会被赋值为下次扩容的阈值
while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {
//条件1: true -> 当前线程获取到的扩容唯一标识戳非本次扩容批次,执行方法体
// false -> 当前线程获取到的扩容唯一标识戳是本次扩容批次,进入下一个判断
//条件2: jdk1.8有bug 这里应该是 sc == (rs << RESIZE_STAMP_SHIFT) + 1
// true -> 表示所有线程都执行完毕了 线程数量是 = 1+n,执行方法体
// false -> 表示还在扩容中,进入下一个判断
//条件3: jdk1.8有bug 这里应该是 sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZERS
// true -> 表示当前参与扩容的线程数量达到最大限度了,不能再参与了,执行方法体
// false -> 表示当前参与扩容的线程数量还未达到最大限度,当前线程可以参与,进入下一个判断
//条件4: true -> 表示扩容已完毕,执行方法体
// false -> 表示扩容还在进行,进入下一个判断
//条件5: true -> 表示全局范围内的任务已经分配完了,执行方法体
// false -> 表示还有任务可分配,结束判断
if ((sc >>> RESIZE_STAMP_SHIFT) != rs ||
sc == rs + 1 ||
sc == rs + MAX_RESIZERS ||
transferIndex <= 0)
break;
//true -> 表示当前线程有任务可分配,将进入协助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
//扩容方法
transfer(tab, nextTab);
break;
}
}
//返回最新的新table引用(可能还在扩容中)
return nextTab;
}
return table;
}
/**
* Returns the stamp bits for resizing a table of size n.
* Must be negative when shifted left by RESIZE_STAMP_SHIFT.
* 返回用于扩容大小为n的表的标识戳。
* 16 -> 32
* n: 16 -> 0000 0000 0000 0000 0000 0000 0001 0000
* Integer.numberOfLeadingZeros(16): 27
* 1: 0000 0000 0000 0000 0000 0000 0000 0001
* 1 << (RESIZE_STAMP_BITS - 1) => 1 << 15: 0000 0000 0000 0000 1000 0000 0000 0000 -> 32768
* --------------------------------------------------------------------------------
* 0000 0000 0000 0000 0000 0000 0001 1011 -> 27
* |
* 0000 0000 0000 0000 1000 0000 0000 0000 -> 32768
* 0000 0000 0000 0000 1000 0000 0001 1011 -> 计算出来的就是返回的标识戳
*/
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
/**
* Moves and/or copies the nodes in each bin to new table. See
* above for explanation.
*/
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//n: 表示扩容前table的长度
//stride: 表示每个线程分配到任务数量
int n = tab.length, stride;
//计算每个线程分配的任务数量,最小值是16
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//true -> 表示当前线程是第一个触发扩容的线程,需要做一些扩容准备
//false -> 表示当前线程是协助扩容的线程
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
//创建一个大一倍的数组 2n
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
//将nt赋值给临时变量nextTab
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
//将临时变量nextTab赋值给全局变量nextTable,为了让协助线程能获取到
nextTable = nextTab;
//记录迁移数据整体进度的标记,index计数是从1开始的
transferIndex = n;
}
//表示新表长度
int nextn = nextTab.length;
//fwd 节点,当某个桶位数据处理完毕后,将此桶位设置为fwd节点,其它写线程 或 读线程看到后会有不同逻辑
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//推进标记
boolean advance = true;
//完成标记
boolean finishing = false; // to ensure sweep before committing nextTab
//i: 表示分配给当前线程任务,执行到的桶位(从后往前执行)
//bound: 表示分配给当前线程任务的下界限制
int i = 0, bound = 0;
//自旋
for (;;) {
//f: 桶位的头结点
//fh: 桶位头结点的hash
Node<K,V> f; int fh;
//1、给当前线程分配任务区间
//2、维护当前线程任务进度(i 表示当前处理的桶位)
//3、维护table 全局范围内的进度(transferIndex)
while (advance) {
//nextIndex: 新分配任务的开始下标(以1开始计数的)
//nextBound: 新分配任务的结束下标
int nextIndex, nextBound;
//条件1: true -> 表示当前线程还未完成所有已分派的任务,--i 表示让线程处理下一个分配的桶位
// false -> 表示当前线程已完成所有已分派的任务 或者 还未分派任务
//条件2: true -> 表示迁移任务已完成,准备做最后的检查操作 注意: 此时条件1的 --i 是控制检查的桶位
if (--i >= bound || finishing)
advance = false;
//前置条件: 当前线程已完成所有已分派的任务 或者 还未分派任务
//条件2: true -> 表示当前任务池中已经没有剩余的任务可分配了
// false -> 表示当前任务池中还有可分配的任务
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
//前置条件: 1.当前线程还未分派任务
// 2.当前任务池中还有可分配的任务
//分配任务 nextBound = 0 表示把剩下的任务都分派给当前线程
//true -> 为当前线程分派任务成功
//false -> 为当前线程分派任务失败,有其它线程抢先分得了任务
else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
bound = nextBound;
//因为transferIndex是以1开始计数的,所以这里需要-1
i = nextIndex - 1;
advance = false;
}
}
//条件1: true -> 表示当前线程未分配到任务
// false -> 进入下一个判断
//条件2、3恒不成立
if (i < 0 || i >= n || i + n >= nextn) {
//sizeCtl临时变量
int sc;
//所有桶位都检查完后执行
if (finishing) {
//将concurrentHashMap.nextTable置空 GC
nextTable = null;
//将新table赋值给全局的table
table = nextTab;
//设置新的扩容阈值为0.75n 2n - n/2 = 3/2n 相当于 n - n/4 = 3/4n 这里用2n是因为扩容后的数组长度为2n
sizeCtl = (n << 1) - (n >>> 1);
//所有迁移任务完成,并检查完毕,正常退出
return;
}
//true -> 表示没有任务分配给当前线程,准备正常退出,sc - 1
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//true -> 当前线程不是最后一个退出的线程
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
//正常退出
return;
//将 finishing、advance 都设置为true,当前线程将要做结束迁移任务的检查操作
finishing = advance = true;
//把i设置成老table的长度,为了从后往前检查是否都迁移完毕
i = n; // recheck before commit
}
}
//tab: 旧table引用
//前置条件: 当前线程任务尚未处理完,正在进行中。
//true -> 说明当前桶位上未存放数据,只需要将此处设置为fwd节点即可,执行方法体。
//false -> 说明当前桶位上有数据,进入下一个判断。
else if ((f = tabAt(tab, i)) == null)
//使用cas把当前节点设置为fed节点
advance = casTabAt(tab, i, null, fwd);
//前置条件: 1、当前线程任务尚未处理完,正在进行中。 2、当前桶位上有数据
//true -> 表示当前桶位上的节点已经迁移成功,再次自旋,进入下一个桶位。
//false -> 表示当前桶位上的节点需要迁移,进入下一个判断。
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
//前置条件: 1、当前线程任务尚未处理完,正在进行中。 2、当前桶位上有数据 3、当前桶位上的节点需要迁移
//执行迁移操作
else {
//给当前桶位上的头结点加锁
synchronized (f) {
//双重判断,防止有其它线程改了头结点,导致上错锁
if (tabAt(tab, i) == f) {
//ln: 表示低位链表的引用
//hn: 表示高位链表的引用
Node<K,V> ln, hn;
//true -> 表示当前桶位是链表桶位
if (fh >= 0) {
//可以获得当前链表 末尾连续高位不变的node
//n恒定为2的次方数,因此,fh & n 只会得到高位是0或者1的二进制数
//获取头节点的迁移标识(0 或 n)
int runBit = fh & n;
//将头结点赋值给lastRun
Node<K,V> lastRun = f;
//p: 当前遍历的节点,从头结点的下一个节点开始
for (Node<K,V> p = f.next; p != null; p = p.next) {
//获取当前节点的迁移标识(0 或 n)
int b = p.hash & n;
//true -> 当前节点的迁移标识与上一个节点的不一致
//false -> 当前节点的迁移标识与上一个节点的一致
if (b != runBit) {
//将runBit设置为当前节点的迁移标识
runBit = b;
//将lastRun设置为当前节点
lastRun = p;
}
}
//末尾连续高位是0
if (runBit == 0) {
//此时lastRun引用的是连续迁移标识是0的低位链表,并将ln指向它
ln = lastRun;
//清空高位链表
hn = null;
}
//末尾连续高位是n
else {
//此时lastRun引用的是连续迁移标识是n的高位链表,并将hn指向它
hn = lastRun;
//清空低位链表
ln = null;
}
//遍历链表到lastRun节点的位置为止
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
//如果迁移标识是0
if ((ph & n) == 0)
//创建一个新的低位链表,并将当前节点放入,且next指针指向旧的低位链表
ln = new Node<K,V>(ph, pk, pv, ln);
else
//创建一个新的高位链表,并将当前节点放入,且next指针指向旧的高位链表
hn = new Node<K,V>(ph, pk, pv, hn);
}
//低位链表设置到新table 原来的桶位上
setTabAt(nextTab, i, ln);
//高位链表设置到新table 原来的桶位 + 扩容大小的桶位上
setTabAt(nextTab, i + n, hn);
//将迁移好的桶位设置为fwd,表示已迁移
setTabAt(tab, i, fwd);
//表示任务继续执行
advance = true;
}
//true -> 表示当前桶位是 红黑树代理节点 treebin
else if (f instanceof TreeBin) {
//将头结点转换为treebin
TreeBin<K,V> t = (TreeBin<K,V>)f;
//低位双向链表 lo 指向低位链表头结点 loTail 指向低位链表尾结点
TreeNode<K,V> lo = null, loTail = null;
//高位双向链表 hi 指向高位链表头结点 hiTail 指向高位链表尾结点
TreeNode<K,V> hi = null, hiTail = null;
//lc 表示低位链表元素数量
//hc 表示高位链表元素数量
int lc = 0, hc = 0;
//遍历treebin中的双向链表 从头结点至尾结点
for (Node<K,V> e = t.first; e != null; e = e.next) {
//当前循环节点的hash值
int h = e.hash;
//用当前循环节点创建出来的新的treeNode节点
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
//将尾指针指向的元素的next指针指向当前循环节点
loTail.next = p;
//将低位链表尾指针指向当前循环节点
loTail = p;
//累加低位链表元素数量
++lc;
}
//当前循环节点属于高位双向链表
else {
//采用尾插法
//说明当前高位链表还没有值
if ((p.prev = hiTail) == null)
//将高位链表头指针指向当前循环节点
hi = p;
//说明当前高位链表有值
else
//将尾指针指向的元素的next指针指向当前循环节点
hiTail.next = p;
//将高位链表尾指针指向当前循环节点
hiTail = p;
//累加高位链表元素数量
++hc;
}
}
//(lc <= 6) 判断低位链表中元素的个数 true -> 不需要树化 false -> 需要树化
//(hc != 0) 判断高位链表中是否有元素 true -> 将低位链表变成红黑树 false -> 用原来低位链表的treebin
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t;
//(hc <= 6) 判断高位链表中元素的个数 true -> 不需要树化 false -> 需要树化
//(lc != 0) 判断低位链表中是否有元素 true -> 将高位链表变成红黑树 false -> 用原来高位链表的treebin
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t;
//将低位链表迁移到新table 与旧表一致 的桶位上
setTabAt(nextTab, i, ln);
//将高位链表迁移到新table 旧表桶位 + 扩容大小 的桶位上
setTabAt(nextTab, i + n, hn);
//将原表对应的迁移好的桶位设置为fwd桶位
setTabAt(tab, i, fwd);
//继续执行,进入下个桶位
advance = true;
}
}
}
}
}
}
putTreeVal()方法是将元素插入到红黑树中。
/**
* Finds or adds a node.
* @return null if added
*/
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
//kc: key的class
Class<?> kc = null;
//searched: 是否有尝试从红黑树中匹配待插入元素的标记
boolean searched = false;
//p: 游标指针 从root节点开始往下查找
TreeNode<K,V> p = root;
for (;;) {
//dir: 表示待插入节点在父节点的左边 或 右边
//ph: 当前游标节点的hash值
//pk: 当前游标节点的key
int dir, ph; K pk;
//true -> 表示当前的树和链表都是空的
if (p == null) {
//将待插入节点封装为treeNode 并同时让 链表的头指针 和 红黑树的头结点指针 指向它
first = root = new TreeNode<K,V>(h, k, v, null, null);
break;
}
//true -> 当前游标节点的hash值比待插入节点的hash值大
else if ((ph = p.hash) > h)
//表示待插入节点位于游标节点的左边
dir = -1;
//true -> 当前游标节点的hash值比待插入节点的hash值小
else if (ph < h)
//表示待插入节点位于游标节点的右边
dir = 1;
//前置条件: 当前游标节点的hash值与待插入节点的hash值相等
//true -> 当前游标节点的key也与待插入节点的key相等
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
//待插入节点已存在,直接返回匹配到的节点
return p;
//前置条件: 1、当前游标节点的hash值与待插入节点的hash值相等
// 2、且当前游标节点的key也与待插入节点的key不相等
//条件1: true -> 通过comparableClassFor(k)也获取不到key的class类型,进入方法体
// false -> 通过comparableClassFor(k)获取到了key的class类型,进入下一个判断
//条件2: true -> 通过compareComparables(kc, k, pk)计算不出dir,进入方法体
// false -> 通过compareComparables(kc, k, pk)计算出dir,结束判断
else if ((kc == null && (kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
//是否有尝试从红黑树中匹配待插入元素
if (!searched) {
TreeNode<K,V> q, ch;
//标记设置为true
searched = true;
//条件1: true -> 迭代当前游标节点的左子树的所有元素 查找到了与待插入节点相同的元素
//条件2: true -> 迭代当前游标节点的右子树的所有元素 查找到了与待插入节点相同的元素
if (((ch = p.left) != null && (q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null && (q = ch.findTreeNode(h, k, kc)) != null))
//返回匹配的元素
return q;
}
//如果还是找不到相同的元素,则计算dir 该方法一定会返回 1 或 -1
dir = tieBreakOrder(k, pk);
}
//xp: 当前游标节点的父节点
TreeNode<K,V> xp = p;
//true -> 表示已经找到需要插入的位置 p指向的位置
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//x: 待插入节点转换为TreeNode类型
//f: 双向链表临时头结点
TreeNode<K,V> x, f = first;
//双向链表的维护 采用头插法 first指针指向待插入节点
first = x = new TreeNode<K,V>(h, k, v, f, xp);
if (f != null)
//将老头结点的prev指针指向新头结点 头插法
f.prev = x;
if (dir <= 0)
//待插入节点位于父节点的左边
xp.left = x;
else
//待插入节点位于父节点的右边
xp.right = x;
//如果父节点是黑色的
if (!xp.red)
//当前节点直接变为红色就可以了 没有破坏红黑数的平衡
x.red = true;
//如果父节点是红色的
else {
//会出现 红红相连 的情况
//给根节点上锁 准备调用平衡方法
lockRoot();
try {
//平衡红黑树,使其再次符合红黑树结构
root = balanceInsertion(root, x);
} finally {
//释放锁
unlockRoot();
}
}
break;
}
}
assert checkInvariants(root);
return null;
}
/**
* Returns the TreeNode (or null if not found) for the given key
* starting at given root.
*/
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
if (k != null) {
//游标节点 初始值为调用该方法的节点
TreeNode<K,V> p = this;
do {
//ph: 当前游标节点的hash值
//dir: 待插入节点位于当前游标节点的 左边 或 右边
//pk: 当前游标节点的key
//q: 匹配到的节点
int ph, dir; K pk; TreeNode<K,V> q;
//pl: 当前游标节点的左子节点
//pr: 当前游标节点的右子节点
TreeNode<K,V> pl = p.left, pr = p.right;
//CASE1: 当前游标节点的hash值 > 待插入节点的hash值
if ((ph = p.hash) > h)
p = pl;
//CASE2: 当前游标节点的hash值 < 待插入节点的hash值
else if (ph < h)
p = pr;
//前置条件: 当前游标节点的hash值 == 待插入节点的hash值
//CASE3: 当前游标节点的key == 待插入的节点的key
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
//CASE4: 当前游标节点的左子节点是null
else if (pl == null)
p = pr;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
//CASE4: 当前游标节点的右子节点是null
else if (pr == null)
p = pl;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
// 3、当前游标节点的左、右节点 != null
//CASE5: 获取待插入节点的key的class类型 并计算出dir
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
// 3、当前游标节点的左、右节点 != null
// 4、无法通过待插入节点key的class类型计算dir
//CASE6: 遍历当前游标节点的整个右子树
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
//前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
// 2、当前游标节点的key != 待插入的节点的key
// 3、当前游标节点的左、右节点 != null
// 4、无法通过待插入节点key的class类型计算dir
// 5、遍历当前游标节点的整个右子树还是未找到
//CASE7: 从左子节点开始继续找
else
p = pl;
} while (p != null);
}
return null;
}
}
treeifyBin()方法是树化操作。
/**
* Replaces all linked nodes in bin at given index unless table is
* too small, in which case resizes instead.
*/
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
//true -> 当前table的长度未达到64,不进行树化操作,执行扩容操作
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
//将table长度扩大一倍
tryPresize(n << 1);
//前置条件: 当前table的长度已达到64
//true -> 说明当前桶位有数据,且是普通的node节点
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
//给链表的头结点上锁
synchronized (b) {
//双重判断,防止有其它线程修改了此头结点,导致锁错对象
if (tabAt(tab, index) == b) {
//hd: 头指针
//tl: 尾指针
TreeNode<K,V> hd = null, tl = null;
//遍历链表 把单向链表转换为双向链表
for (Node<K,V> e = b; e != null; e = e.next) {
//将当前循环节点转换为双向链表
TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null);
//当前双向链表为空
if ((p.prev = tl) == null)
//将头指针指向当前循环节点
hd = p;
//当前双向链表有值
else
//将尾指针指向的节点的next指针指向当前循环节点
tl.next = p;
//把尾指针指向当前循环节点
tl = p;
}
//new TreeBin<K,V>(hd)) 将双向链表转换为红黑树
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
/**
* Tries to presize table to accommodate the given number of elements.
*
* @param size number of elements (doesn't need to be perfectly accurate)
*/
private final void tryPresize(int size) {
//(size >= (MAXIMUM_CAPACITY >>> 1)) 判断需要创建的ConcurrentHashMap的大小是否超过最大值的一半
//ture -> c = MAXIMUM_CAPACITY 最大值
//false -> c = tableSizeFor(size + (size >>> 1) + 1) 能保证在插入指定数量的元素前不会触发扩容 牺牲内存,保证性能
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1);
//sc: sizeCtl临时变量 默认是16
int sc;
//自旋 当 sc<0 时退出
while ((sc = sizeCtl) >= 0) {
//tab: table引用
//n: 数组长度
Node<K,V>[] tab = table; int n;
//CASE1: 当前table还未初始化
if (tab == null || (n = tab.length) == 0) {
//获取sc 和 c 中较大的那位
n = (sc > c) ? sc : c;
//sizeCtl设置为 -1 表示加锁
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//双重验证
if (table == tab) {
@SuppressWarnings("unchecked")
//创建一个长度为n的链表
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//将新链表赋值给table
table = nt;
//设置下次扩容阈值 0.75n
sc = n - (n >>> 2);
}
} finally {
//sizeCtl = 下次扩容阈值
sizeCtl = sc;
}
}
}
//前置条件: 当前table已经初始化
//CASE2: 扩容大小 <= 扩容阈值 或 当前table长度已经是最大值了
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
//前置条件: 1、当前table已经初始化
// 2、当前table准备扩容
//CASE3: 当前table还未发生变化
else if (tab == table) {
int rs = resizeStamp(n);
//true -> 表示table扩容中
if (sc < 0) {
Node<K,V>[] nt;
//条件1: true -> 当前线程获取到的扩容唯一标识戳非本次扩容批次,执行方法体
// false -> 当前线程获取到的扩容唯一标识戳是本次扩容批次,进入下一个判断
//条件2: jdk1.8有bug 这里应该是 sc == (rs << RESIZE_STAMP_SHIFT) + 1
// true -> 表示所有线程都执行完毕了 线程数量是 = 1+n,执行方法体
// false -> 表示还在扩容中,进入下一个判断
//条件3: jdk1.8有bug 这里应该是 sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZERS
// true -> 表示当前参与扩容的线程数量达到最大限度了,不能再参与了,执行方法体
// false -> 表示当前参与扩容的线程数量还未达到最大限度,当前线程可以参与,进入下一个判断
//条件4: true -> 表示扩容已完毕,执行方法体
// false -> 表示扩容还在进行,进入下一个判断
//条件5: true -> 表示全局范围内的任务已经分配完了,执行方法体
// false -> 表示还有任务可分配,结束判断
if ((sc >>> RESIZE_STAMP_SHIFT) != rs ||
sc == rs + 1 ||
sc == rs + MAX_RESIZERS ||
(nt = nextTable) == null ||
transferIndex <= 0)
break;
//当前线程协助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//true -> 当前线程是第一个执行扩容的线程
else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
}
}
}
addCount()方法有2个作用,1、统计当前table一共有多少元素。 2、判断是否达到扩容标准,触发扩容。
/**
* Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
*
* @param x the count to add
* @param check if <0, don't check resize, if <= 1 only check if uncontended
*/
private final void addCount(long x, int check) {
//x: 需要累加的值
//check: 0: 表示当前table中对应桶位只有头节点 >=1: 表示当前table中对应桶位是链表 2: 表示当前table中对应桶位是树
//as: cells数组引用
//b: baseCount
//s: 统计table大小
CounterCell[] as; long b, s;
//条件1: true -> 表示cells数组已经创建,进入方法体内。
// false -> 表示cells数组还未创建,进行下一个判断。
//条件2: true -> 表示通过cas把值累加到baseCount失败,说明出现了竞争,进入方法体内。
// false -> 表示通过cas把值累加到baseCount成功,结束当前判断。
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
//a: 表示当前线程寻址命中的cell
//v: 匹配到的cells数组对应桶位上的cell节点的value
//m: cells数组长度
CounterCell a; long v; int m;
//uncontended: true -> 未发生竞争 false -> 发生竞争
boolean uncontended = true;
//场景1: cells数组已经创建。
//场景2: cells数组未创建,但是在累加到baseCount时发生了竞争。
//条件1: true -> 表示cells数组还未创建(通过场景2进入的),进入方法体 重试将传入的值累加到baseCount。
// false -> 表示cells数组已经创建,进入下一个判断。
//条件2: true -> 表示匹配到的桶位上还未初始化cell节点,进入方法体 初始化cell。
// false -> 表示匹配到的桶位上已经初始化cell节点,进入下一个判断。
//条件3: true -> 表示当前线程通过cas把传入的值累加到对应cells数组桶位上的cell节点失败,说明发生了竞争,进入方法体 cells数组扩容 或者 重试将传入的值累加到baseCount。
// false -> 表示当前线程通过cas把传入的值累加到对应cells数组桶位上的cell节点成功,结束当前判断。
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
//初始化cell 或者 cells数组扩容 或者 重试将传入的值累加到baseCount
//注意:uncontended为 false 才会今入方法体
fullAddCount(x, uncontended);
//考虑到fullAddCount会占用线程很长一段处理时间,因此不需要继续参与下面的操作
return;
}
//只有当 check > 1 时,表示对应桶位新增元素后是链表或者树,才需要检查是否需要扩容
if (check <= 1)
return;
//获取baseCount和每个cell节点中的value总和(这里只是拿到期望值,并不是最终值,得到结果后还可能有其它线程继续累加)
s = sumCount();
}
//下面是table扩容方法
//表示一定是put调用的addCount
if (check >= 0) {
//tab: map.table引用
//nt: 需要迁移的table
//n: table数组长度
//sc: sizeCtl的临时值
Node<K,V>[] tab, nt; int n, sc;
//s: table中元素的个数
//sizeCtl: CASE1: <0 表示当前table数组正在进行扩容,高16位表示:扩容的标识戳 低16位表示:(1 + nthread) 当前参与扩容的线程数量
// CASE2: >0 表示下次触发时的扩容条件(阈值)
//条件1: true -> CASE1: 当前sizeCtl为负数,表示当前table数组正在进行扩容
// CASE2: 当前sizeCtl为正数,表示当前table中元素个数已经达到扩容条件
// false -> 表示当前table中元素个数没有达到扩容条件 或者 扩容完毕了
//条件2: 恒成立
//条件3: true -> 当前table长度没有超过最大限制
// false -> 当前table长度超过最大限制
while (s >= (long)(sc = sizeCtl) &&
(tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//扩容批次唯一标识戳
//比如: 16 -> 32 标识戳 = 0b 1000 0000 0001 1011
int rs = resizeStamp(n);
//true -> 表示当前table正在扩容,进入方法体。
//false -> 表示当前table需要扩容,但是还未有线程操作,进入下一个判断。
if (sc < 0) {
//条件1: true -> 当前线程获取到的扩容唯一标识戳非本次扩容批次,执行方法体
// false -> 当前线程获取到的扩容唯一标识戳是本次扩容批次,进入下一个判断
//条件2: jdk1.8有bug 这里应该是 sc == (rs << RESIZE_STAMP_SHIFT) + 1
// true -> 表示所有线程都执行完毕了 线程数量是 = 1+n,执行方法体
// false -> 表示还在扩容中,进入下一个判断
//条件3: jdk1.8有bug 这里应该是 sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZERS
// true -> 表示当前参与扩容的线程数量达到最大限度了,不能再参与了,执行方法体
// false -> 表示当前参与扩容的线程数量还未达到最大限度,当前线程可以参与,进入下一个判断
//条件4: true -> 表示扩容已完毕,执行方法体
// false -> 表示扩容还在进行,进入下一个判断
//条件5: true -> 表示全局范围内的任务已经分配完了,执行方法体
// false -> 表示还有任务可分配,结束判断
if ((sc >>> RESIZE_STAMP_SHIFT) != rs ||
sc == rs + 1 ||
sc == rs + MAX_RESIZERS ||
(nt = nextTable) == null ||
transferIndex <= 0)
//当前线程无需参与扩容,退出自旋
break;
//前置条件: table正在扩容中,当前线程可以参与扩容
//true -> 当前线程获取 参与扩容的资格成功,执行方法体。
//false -> 当前线程获取参与扩容的资格失败,进入下一次自旋
if (U.compareAndSwapInt(this, SIZECTL, sc,sc + 1))
//协助其它线程扩容table,持有nextTable引用
transfer(tab, nt);
}
//RESIZE_STAMP_SHIFT: 16
//rs << RESIZE_STAMP_SHIFT: 1000 0000 0001 1011 0000 0000 0000 0000
//+2: 1000 0000 0001 1011 0000 0000 0000 0010 -> 表示低位存储线程数量 n+1
//前置条件: 表示当前table需要扩容
//true -> 表示当前线程是参加扩容的第一个线程,进入方法体。
//false -> CASE1: 有其它线程抢先成为参加扩容的第一个线程修改了sizectl,导致cas失败,结束判断。
// CASE2: 有其它线程在执行transfer()后修改了sizectl,导致cas失败,结束判断。
else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))
//第一个线程开始扩容table,不持有nextTable
transfer(tab, null);
//重新获取table元素数量,准备开始下一次自旋
s = sumCount();
}
}
}
// See LongAdder version for explanation
private final void fullAddCount(long x, boolean wasUncontended) {
//x: 要累加的值
//wasUncontended: 是否没有发生竞争 传入恒为false
//h: 当前线程的hash值
int h;
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
//将wasUncontended强制设为 true 表示当前无竞争
wasUncontended = true;
}
//表示扩容意向
boolean collide = false; // True if last slot nonempty
//自旋
for (;;) {
//as: cells数组引用
//a: cells数组桶位上的cell
//n: cells数组长度
//v: cell中的value
CounterCell[] as; CounterCell a; int n; long v;
//true -> cells数组不为null
if ((as = counterCells) != null && (n = as.length) > 0) {
//前置条件: cells数组不为null
//true -> 当前线程匹配到的桶位上还未初始化cell
if ((a = as[(n - 1) & h]) == null) {
//true -> 可以尝试获取锁,表示此时没有线程在操作cells数组
if (cellsBusy == 0) { // Try to attach new Cell
//新建cell节点
CounterCell r = new CounterCell(x); // Optimistic create
//条件1: true -> 双重验证,可以尝试获取锁,进入下一个判断
// false -> 双重验证,有线程抢占锁了,进入下次自旋
//条件2: true -> 获取锁成功,进入方法体
// false -> 双重验证,有线程抢占锁了,进入下次自旋
if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
//是否已经创建cell
boolean created = false;
try { // Recheck under lock
//再次校验cells数组是否已创建且有值、当前线程匹配到的桶位上是否为null
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
//将创建好的cell节点复制到对应桶位上
rs[j] = r;
//表示创建成功
created = true;
}
} finally {
//释放锁
cellsBusy = 0;
}
//创建成功,退出自旋
if (created)
break;
//当前桶位上已有cell 可能有线程抢先创建了cell,进入下次自旋
continue; // Slot is now non-empty
}
}
//设置扩容意向为false
collide = false;
}
//前置条件: 1、cells数组不为null
// 2、当前桶位上已经初始化cell
//true -> 发生了竞争
else if (!wasUncontended) // CAS already known to fail
//表示将标志重置为无竞争,rehash后再尝试累加
wasUncontended = true; // Continue after rehash
//前置条件: 1、cells数组不为null 2、当前桶位上已经初始化cell 3、wasUncontended == true 表示没有发生竞争
//true -> 累加x到cell 成功
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
//退出自旋
break;
//前置条件: 1、cells数组不为null 2、当前桶位上已经初始化cell 3、wasUncontended == true 表示没有发生竞争 4、累加x到cell 失败,表示发生竞争
//条件1: true -> cells数组发生了扩容,进入方法体
// false -> cells数组与临时变量的值一致,进入下一个判断
//条件2: true -> cells数组的长度 >= 电脑cpu数量,进入方法体
// false -> cells数组的长度 < 电脑cpu数量,进入下一个判断
else if (counterCells != as || n >= NCPU)
//设置扩容意向为false,进入下次自旋
collide = false; // At max size or stale
//前置条件: 1、cells数组为null 2、当前桶位上已经初始化cell 3、wasUncontended == true 表示没有发生竞争
// 4、累加x到cell 失败,表示发生竞争 5、cells数组可扩容
//true -> 扩容意向为false
else if (!collide)
//设置扩容意向为true,进入下次自旋,准备扩容
collide = true;
//前置条件: 1、cells数组为null 2、当前桶位上已经初始化cell 3、wasUncontended == true 表示没有发生竞争
// 4、累加x到cell 失败,表示发生竞争 5、cells数组可扩容 6、扩容意向为true
//true -> 获取锁成功 准备进行扩容
else if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {// Expand table unless stale
//将cells数组扩大一倍
CounterCell[] rs = new CounterCell[n << 1];
//迁移数据
for (int i = 0; i < n; ++i)
rs[i] = as[i];
//更新数组引用
counterCells = rs;
}
} finally {
//释放锁
cellsBusy = 0;
}
//设置扩容意向为false
collide = false;
//扩容后,进入下次自旋
continue; // Retry with expanded table
}
//rehash 进入下次自旋
h = ThreadLocalRandom.advanceProbe(h);
}
//前置条件: cells数组为null 表示还未初始化cells数组
//条件1: true -> 可获取锁
// false -> 目前有线程竞争锁
//条件2: true -> cells数组没有进行初始化
// false -> cells数组已经被其它线程初始化
//条件3: true -> 获取到锁
// false -> 遇到竞争,获取锁失败
else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
//是否初始化完成
boolean init = false;
try { // Initialize table
//双重判断
if (counterCells == as) {
//初始化长度为2的cells数组
CounterCell[] rs = new CounterCell[2];
//将cell插入到 二选一 的桶位上
rs[h & 1] = new CounterCell(x);
//更新cells数组引用
counterCells = rs;
//表示已完成初始化
init = true;
}
} finally {
//释放锁
cellsBusy = 0;
}
//完成初始化,退出自旋
if (init)
break;
}
//前置条件: 1、cells数组为null 2、cells数组正在初始化中
//true -> 将累加值累加到 base 中
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
final long sumCount() {
//as: cells数组的引用
//a: cell节点
CounterCell[] as = counterCells; CounterCell a;
//baseCount引用
long sum = baseCount;
if (as != null) {
//将所有cell中的value之和 + baseCount
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
//返回暂时统计出的值(不是最终的值)
return sum;
}