继承和实现的类、接口
继承AbstractMap<K,V>抽象父类
实现ConcurrentMap<K,V>(map规范)、Serializble(序列化)
成员常量
// 表最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 表默认容量
private static final int DEFAULT_CAPACITY = 16;
// 数组最大容量
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 默认并发数量
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 负载因子
private static final float LOAD_FACTOR = 0.75f;
// 链表转换为红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;
// 红黑树转链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
// 链表转红黑树的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 每次进行转移的最小值
private static final int MIN_TRANSFER_STRIDE = 16;
// 生成sizeCtl所使用的bit位数
private static int RESIZE_STAMP_BITS = 16;
// 帮助扩容时使用的元素,最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 帮助扩容时使用的元素,记录sizeCtl中的大小所需进行的偏位移数
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
/*
* Encodings for Node hash fields. See above for explanation.
*/
static final int MOVED = -1; // hash值是-1,表示这是一个forwardNode节点,表示有线程处理过了
static final int TREEBIN = -2; // hash值是-2 表示这时一个TreeBin节点,表示这是一个树节点
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
成员变量
// 存储元素的数组,总是2的幂次倍
transient volatile Node<K,V>[] table;
private transient volatile Node<K,V>[] nextTable;
// 计数
private transient volatile long baseCount;
//hash表初始化或扩容时的一个控制位标识量。
//负数代表正在进行初始化或扩容操作
//-1代表正在初始化
//-N 表示有N-1个线程正在进行扩容操作
//正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小
private transient volatile int sizeCtl;
// 扩容下 另一个表的索引
private transient volatile int transferIndex;
// 旋转锁
private transient volatile int cellsBusy;
// countterCell表
private transient volatile CounterCell[] counterCells;
// 视图
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;
添加/修改put()
// 参数不能为null
public V put(K key, V value) {
// 调用putVal()完成添加(修改)操作
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 参数为空直接返回空指针异常
if (key == null || value == null) throw new NullPointerException();
// key.hashCode获取hash值,用spread()计算下标位置
int hash = spread(key.hashCode());
// 记录单个桶中的元素个数
int binCount = 0;
// 循环使用了CAS机制,不成功则一直循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 判断数组是否为空或长度是否为0
if (tab == null || (n = tab.length) == 0)
// 对数组进行初始化
tab = initTable();
// tabAt(数组,下标)用来获取指定下标的内容,内部是Unsafe的getObjectVolatile方法,保证线程拿到的是table中的最新元素。
// (n-1)&hash计算下标
// 如果为空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 通过CAS方式替换下标i的节点
// casTabAt(数组,下标,目标值,要修改的值)
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 发生了hash冲突,数组正在进行扩容
else if ((fh = f.hash) == MOVED)
// 协助扩容
tab = helpTransfer(tab, f);
// 以上条件都不满足,即底层数组存在且计算出的数组下标有值且数组没有进行扩容,则去添加/替换元素
else {
V oldVal = null;
// 结点上锁,这里的结点可以理解为hash值相同组成的链表的头结点
// 也代表了ConcurrentHashMap是线程安全的
synchronized (f) {
// 判断是否为头节点
if (tabAt(tab, i) == f) {
// 判断是否为链表
if (fh >= 0) {
binCount = 1;
// 对链表进行遍历
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 当前节点的hash值和要插入的hash值,key相同
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 应该是记录当前节点旧值
oldVal = e.val;
// 传入的值为false,这里为true
if (!onlyIfAbsent)
// 新值替换旧值
e.val = value;
break;
}
Node<K,V> pred = e;
// 这里是整条链表都没找到相同的key,已经走到了链表底部
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;
// 这里调用的是内部的TreeBin的putTreeVal,不是hashMap的
// 返回值不为空,代表找到了相同的key,为该节点的value值
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
// 记录旧值
oldVal = p.val;
// 这里传入false,为true
if (!onlyIfAbsent)
// 对旧值进行替换
p.val = value;
}
}
}
}
// binCount记录了遍历了的节点数,如果节点数不为0
if (binCount != 0) {
// 判断节点数量是否大于树化条件
if (binCount >= TREEIFY_THRESHOLD)
// 执行树化操作
// 树化,这里还会判断一下整体容量是否大于64.如果小于64进行扩容不树化,这里会再次加锁
treeifyBin(tab, i);
// 判断旧值是否为空
if (oldVal != null)
// 返回旧值
return oldVal;
break;
}
}
}
// 维护数组长度(条件允许时会扩容)
addCount(1L, binCount);
return null;
}
数组维护addCount()
private final void addCount(long x, int check) {
// CounterCell用来记录元素个数,总数之的分值分别记录在每个cell中
// CounterCell的每个元素,都存储一个元素个数,我们使用的size()就是通过循环CounterCell累加而得到的
CounterCell[] as; long b, s;
// CounterCell数组是否为空
if ((as = counterCells) != null ||
// CounterCell为空,则使用cas对baseCount进行+操作
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
// 如果CounterCell数组不为空,或者CounterCell数组为空且使用CAS对baseCount进行加操作失败,进入这里
CounterCell a; long v; int m;
// 标记是否 没有 多线程竞争,这里的true为无竞争
boolean uncontended = true;
// 如果CounterCell数组为空
if (as == null || (m = as.length - 1) < 0 ||
// 随机计算一个CounterCell数组中某个位置的对象为空
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
// 或者对该CounterCell的value值使用CAS相加时失败 uncontended=false 表示有竞争
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 任何一个条件成功都会执行
// fullAddCount()主要用来初始化CounterCell
fullAddCount(x, uncontended);
return;
}
// 使用CAS修改CELLVALUE成功,则会到这里
// check为binCount binCount始终大于等于0
// binCount等于0 表示在put值时Node数组对应的元素为空
// binCount等于1 表示在put值时,是链表且链表只有一个key相同的元素,替换
// 直接return,不需要再去检查是否需要扩容
if (check <= 1)
return;
// 计算此时的map大小
s = sumCount();
}
// 判断是否需要扩容
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 如果元素个数>=扩容阈值或者-1
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 获取一个扩容的标记
int rs = resizeStamp(n);
// <0则代表其他线程正在扩容,sc为sizeCtl
if (sc < 0) {
//这5个条件只要有一个条件为true,说明当前线程不能帮助进行此次的扩容,直接跳出循环
//sc >>> RESIZE_STAMP_SHIFT!=rs 表示比较高RESIZE_STAMP_BITS位生成戳和rs是否相等,相同
//sc=rs+1 表示扩容结束
//sc==rs+MAX_RESIZERS 表示帮助线程线程已经达到最大值了
//nt=nextTable -> 表示扩容已经结束
//transferIndex<=0 表示所有的transfer任务都被领取完了,没有剩余的hash桶来给自己自己好这个线程来做transfer
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);
}
// 没有扩容,(rs << RESIZE_STAMP_SHIFT) +2 表示只有一个线程正在进行扩容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
// 扩容
transfer(tab, null);
// 从新计数,从而判断是否需要下一轮扩容
s = sumCount();
}
}
}
扩容机制transfer()
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
// stride和CPU相关
int n = tab.length, stride;
// 先判断CPU核数是否大于1 如果不大于1则没有必要多线程扩容
// 计算需要迁移多少个hash桶,小于16的话直接赋值16
// 这里的目的是让每一个CPU处理的桶数量一样,避免出现转移任务分布不均匀。桶较少时,默认一个CPU处理16个桶
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
// 外围会保证第一个发起扩容请求的线程调用此方法时参数nextTable为null,即新表未初始化
// initiating只能有一个线程进行构造nextTable,如果别的线程进入发现不为空就不用构造nextTable了
if (nextTab == null) {
try {
@SuppressWarnings("unchecked")
// 容量翻倍
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {
// 扩容失败,sizeCtl使用Integer最大值
sizeCtl = Integer.MAX_VALUE;
return;
}
// 将新的数组赋值给成员变量
nextTable = nextTab;
// n为旧tab的length
transferIndex = n;
}
// 新数组的长度
int nextn = nextTab.length;
// 创建一个新的fwd节点,用于占位,别的线程发现这个位置是ForwardingNode节点时,跳过
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// 值为true时,表示该节点已经处理过了,说明需要进行下一个下标(i--)
// 值为false时,就不能进行下一个下标,需要将当前下标处理完毕才能继续推进
boolean advance = true;
// 如果为true,则此方法结束
boolean finishing = false; // to ensure sweep before committing nextTab
// 死循环,i是数组索引下标,bound为边界(当前线程可以处理的当前桶区间最小下标)
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// 这里有点绕
// 当advance=true时才可以进行下一个位置的迁移操作(i--)
// i指向transferIndex,dound指向transferIndex-stried
while (advance) {
int nextIndex, nextBound;
// i先自减1,当i<dound或者最后一个线程转移的操作结束
// advance赋值为false,不在计算i和bound
if (--i >= bound || finishing)
// 防止在没有处理成功一个桶的情况下进行下一轮推送
advance = false;
// 这里有两层意思
// 1、当一个线程进入时,获取最新的转移下标
// 2、线程处理完自己的区间后,如果还有剩余区间,则再次获取
else if ((nextIndex = transferIndex) <= 0) {
// transferIndex <= 0 时,代表没有区间了,扩容结束,当前线程退出
i = -1;
advance = false;
}
// cas机制修改transferIndex
// 判断REANSFERINDEX和nextIndex是否相等
// nextbound为此次线程迁移任务的边界,从后往前执行
// 下一个索引位置大于区块末尾,则nextIndex减去前一个区块,否则为0
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
// 这个值就是当前线程可以处理的最小当前区间最小下标
bound = nextBound;
// 初次对i 赋值,这个就是当前线程可以处理的当前区间的最大下标
i = nextIndex - 1;
// 防止当前线程没有成功处理一个桶的情况下进行下一轮推送
advance = false;
}
}
// i<0(不在table下标内,按上面判断的意思,是领取最后一段区间的线程扩容结束)
// i>= (n=table.length),这里的i应该为当前线程处理hash桶结束的位置
// i+n >= nextn,nextn在上面容量进行了翻倍操作,但是长度和table长度一样
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 完成了扩容
if (finishing) {
// 赋予空值
nextTable = null;
// 将新表赋值给全局table
table = nextTab;
// 更新扩容阈值,为现在容量的0.75倍
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// 在addcount()中,用了(rs << RESIZE_STAMP_SHIFT) + 2,然后每多一个线程参与进来sc+1
// 所以在这里要对sc-1,表示该线程任务已经结束
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 如果sc-2不等于标识符左移16位,说明该线程不是最后一个扩容的线程
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
// 结束此线程
return;
// 将finishing变量更新为true
finishing = advance = true;
// 再次循环检查表
i = n; // recheck before commit
}
}
// 判断老tab中i下标位置是否为空
else if ((f = tabAt(tab, i)) == null)
// 为空,使用cas方式将该位置放入一个fwd节点,再次推进一个下标
advance = casTabAt(tab, i, null, fwd);
// 如果该节点的hash值为moved
else if ((fh = f.hash) == MOVED)
// 说明别的线程已经处理过了,再次推进一个下标
advance = true;
// 走到这里,说明这个位置上有值存在且不是占位符,
else {
// 对该节点上锁,防止putVal向链表插入数据
synchronized (f) {
// 这里的判断可能会漏掉某一个桶,进行下一个桶的数据迁移。
// 判断i下标处的桶节点是否和f相同
if (tabAt(tab, i) == f) {
// 低位桶,高位桶
Node<K,V> ln, hn;
// fh为f的hash值,大于0时,TreeBin的hash是-2,代表是链表节点
if (fh >= 0) {
// 这里的链表拆分逻辑,我把莫纳-鲁道博主的介绍放在这里,大家看一下方便理解
/* 1、对老长度进行与运算(第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0);
2、由于 Map 的长度都是 2 的次方(000001000 这类的数字),那么取于 length 只有 2 种结果,一种是 0,一种是1;
3、如果是结果是0 ,Doug Lea 将其放在低位,反之放在高位,目的是将链表重新 hash,放到对应的位置上,让新的取于算法能够击中他。*/
int runBit = fh & n;
Node<K,V> lastRun = f;
// 先遍历一遍这个桶,lastRun机制,并且计算runBit
for (Node<K,V> p = f.next; p != null; p = p.next) {
// 取于桶中每个节点的hash值
int b = p.hash & n;
// 如果节点的hash值和首节点hash值不相同
if (b != runBit) {
// 更新runBit,用于判断lastRun该赋值给ln还是hn
runBit = b;
// lastRun用于保证后面的节点与自己的值相同,避免进行不必要的循环
lastRun = p;
}
}
// 最后更新的runBit等0,赋值给低位桶
if (runBit == 0) {
ln = lastRun;
hn = null;
}
// runbit不等0,赋值给高位桶
else {
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)
// 创建低位节点
ln = new Node<K,V>(ph, pk, pv, ln);
else
// 创建高位节点
hn = new Node<K,V>(ph, pk, pv, hn);
}
// 设置低位节点放在新链表的i处
setTabAt(nextTab, i, ln);
// 设置高位节点放在新链表的i+n处
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) {
// 下一节点hash值
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
// 和链表的判断相同,等0的放低位
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
// 不等0,放高位
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
// 节点数<=6,转换为链表。否则转换为新的红黑树
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;
}
}
}
}
}
}
协助扩容helpTransfer
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// tab不为null且node节点为转移类型fwd且ndoe节点的下一个表(新表)不为空
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// 根据当前表的length得到一个标识符号(扩容标识戳)
int rs = resizeStamp(tab.length);
// nextTab还没有被修改且tab没被修改
// sizeCtl < 0,说明正在扩容
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
// 如果 sizeCtl 无符号右移 16 不等于 rs ( sc前 16 位如果不等于标识符,则标识符变化了)
// 或者 sizeCtl == rs + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// 或者 sizeCtl == rs + 65535 (如果达到最大帮助线程的数量,即 65535)
// 或者转移下标正在调整 (扩容结束)
// 结束循环,返回 table
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// 如果都不是,将sizeCtl + 1,表示增加了一个线程帮助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
// 调用扩容方法进行转移
transfer(tab, nextTab);
// 结束循环
break;
}
}
return nextTab;
}
return table;
}
查找get()
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 拿到key的hash值
int h = spread(key.hashCode());
// 如果表不为空且表长度>0且key所在的桶不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 如果桶所在节点的hash值和key的hash值相同
if ((eh = e.hash) == h) {
// 判断当前key是否为桶的头节点
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
// 返回value值
return e.val;
}
// eh<0说明当前节点有两种可能,1、fwd节点;2、树节点
else if (eh < 0)
// 在桶中找到返回value值,否则返回空
return (p = e.find(h, key)) != null ? p.val : null;
// 所在桶的节点的下一个节点不为空,循环
while ((e = e.next) != null) {
// 如果节点的hash值和目标key的hash值相同且key相等
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
// 返回当前节点的value值
return e.val;
}
}
return null;
}
删除remove()
public V remove(Object key) {
return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
// 获取要删除的key的hash值
int hash = spread(key.hashCode());
// 把当前表赋值给tab
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 表不为空且长度不为0
if (tab == null || (n = tab.length) == 0 ||
// key的hash值所对应的位置节点是否为空
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
// 如果hash值为MOVED,说明有线程正在进行扩容
else if ((fh = f.hash) == MOVED)
// 帮助扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
// 对当前节点进行加锁
synchronized (f) {
// 如果i下标处的节点和f相同
if (tabAt(tab, i) == f) {
// 判断是否为链表
if (fh >= 0) {
validated = true;
// 将头部节点赋值给当前节点,自旋
for (Node<K,V> e = f, pred = null;;) {
K ek;
// 当前节点的hash值是否和要删除的key的hash值相同
if (e.hash == hash &&
// key是否相同
((ek = e.key) == key ||
// key不为空且值是否相等
(ek != null && key.equals(ek)))) {
// 当前节点的val值
V ev = e.val;
// 调用remove时这里的参数cv是null
// 传入的cv 和当前节点值相等
if (cv == null || cv == ev ||
// 当前节点的val不为空且传入的cv和当前节点val值相同
(ev != null && cv.equals(ev))) {
// 将当前节点val值设置为旧节点
oldVal = ev;
// 如果传入的value不为空
if (value != null)
// 赋值给当前节点
e.val = value;
// 如果传入的pred不为空,即前置指针
// 即当前节点不是第一个节点
else if (pred != null)
// 将当前节点的上一个节点的node.next指向当前节点的下一个节点,使当前节点没有被其他节点引用,最后被GC掉
pred.next = e.next;
else
// 当前节点是头节点,那么就通过cas将头节点指向第二个节点,使头节点没有被其他节点引用,最后被GC掉
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
// 没有找到当前key对应的节点,继续往后遍历
if ((e = e.next) == null)
break;
}
}
// 当前节点为红黑树
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
// 如果树节点的根不为空,且指定的key和hash不为空
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
// 将当前节点的val值保存
V pv = p.val;
// 如果cv为null,或者传入cv值和找到的pv值相等
if (cv == null || cv == pv ||
// 当前节点val不为空且val和传入的cv相同
(pv != null && cv.equals(pv))) {
// 将pv(p.val)赋值给旧值
oldVal = pv;
// 如果传入的value值不为null
if (value != null)
// 将value的值给到当前节点p的值
p.val = value;
// 如果能够删除当前节点p返回true
else if (t.removeTreeNode(p))
// 执行树转链表
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
// 如果有其他线程改变了头节点
// 此时 validated 为 false,当前线程会进入下一个for循环
// 而不会走这里面的退出逻辑
// 即validated是在synchrinized里面被改为true的,所以每次只有一个线程可以进入该逻辑
if (validated) {
// 如果旧值为空说明table中有这个值,那么就删除成功了,则桶的node数减1
if (oldVal != null) {
// 如果传入的value为空
if (value == null)
// 将节点数量-1
addCount(-1L, -1);
// 返回旧值
return oldVal;
}
break;
}
}
}
return null;
}