Java8 ConcurrentHashMap 原理详解
目录
1:简介
1.1 ConcurrentHashMap 结构
JDK 1.8 的 HashMap 的数据结构如上图所示,当链表节点较少时仍然是以链表存在,当链表节点较多时(大于8)会转为红黑树。图片引用自(https://blog.csdn.net/v123411739/article/details/78996181)
1.2 约定
熟悉HashMap的原理,熟悉CAS技术,熟悉Java并发基础知识,可以参考以下文章
HashMap https://blog.csdn.net/v123411739/article/details/78996181
CAS https://www.jianshu.com/p/ae25eb3cfb5d
如果没有特殊说明,下文说的table表示ConcurrentHashMap 中的数组,n表示数组的长度
1.3 重要成员变量简介
// 对应1.1结构中的table数组
transient volatile Node<K,V>[] table;
// 在扩容时会新建一个容量为 2 * n 的数组nextTable
private transient volatile Node<K,V>[] nextTable;
// HashMap总结点数由baseCount 和 counterCells 一起记录
private transient volatile long baseCount;
// 在初始化table时,sizeCtl = -1
// 初始化完成后或者扩容完成后,sizeCtl = 0.75 * n
// 在扩容时 sizeCtl = (rs << RESIZE_STAMP_SHIFT) + 2 (下文讲扩容时会详解)
private transient volatile int sizeCtl;
// 用于协助扩容的参数
private transient volatile int transferIndex;
// 记录counterCells是否存在竞争
private transient volatile int cellsBusy;
// 协助记录HashMap节点数的数组
private transient volatile CounterCell[] counterCells;
// table最大长度
private static final int MAXIMUM_CAPACITY = 1 << 30;
// table 默认长度,初始化时为16
private static final int DEFAULT_CAPACITY = 16;
// 负载因子
private static final float LOAD_FACTOR = 0.75f;
// 链表长度超过这个值转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 红黑树节点数小于这个值转换成链表
static final int UNTREEIFY_THRESHOLD = 6;
// 当数组长度小于64时,如果发现链表应该转换成红黑树,此时并不转换,而是对table进行扩容
static final int MIN_TREEIFY_CAPACITY = 64;
// 以下四个是在扩容时用到,介绍扩容过程时会详解
private static final int MIN_TRANSFER_STRIDE = 16;
private static int RESIZE_STAMP_BITS = 16;
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
值得留意的是这里有很多字段都是被volatile修改的,保证了它们的可见性
2 put 方法
2.1 put方法简要流程
2.2 put 方法源码
结合2.1的流程图可以详细分析put方法
// put方法最终是调用putVal方法
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key不能为空,value不能为哦那个
if (key == null || value == null) throw new NullPointerException();
// 计算key的hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 如果table为空,那么初始化table
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// (n - 1) & hash 算出下标i
// 如果table[i]节点为空,那么新建链表类型节点CAS赋值,赋值成功的直接退出循环
// 赋值失败的化进入下一轮循环,使用CAS能提高效率
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
}
// 当table[i].hash = -1 时,表示当前有线程正在进行扩容操作,那么协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 锁住table[i] 节点
synchronized (f) {
if (tabAt(tab, i) == f) {
// 如果table[i] 是链表类型节点
// 遍历链表进行覆盖或者插入
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;
}
}
}
// 如果table[i] 红黑树类型节点,那么调用红黑树putTreeVal方法插入
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) {
// 如果链表节点长度大于TREEIFY_THRESHOLD(8)
// 那么扩容或者转换成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 总结点数加一,并且需要检查扩容
addCount(1L, binCount);
return null;
}
接下来看看是如何初始化table的,由于比较浅显易懂,不作详细分析
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// 如果sizeCtl 小于0,说明当前正在初始化或者扩容
// 那么提示让出线程
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// 将 sizeCtl 设置成 -1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 新建 Node 类型数组nt
// 第一次创建的话容量为DEFAULT_CAPACITY(16)
// 将nt 赋值给table变量
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 = 0.75 * n
sc = n - (n >>> 2);
}
} finally {
// 将sc 赋值给sizeCtl
sizeCtl = sc;
}
break;
}
}
return tab;
}
插入链表节点和红黑树节点和HashMap差异不大,这里不再作解析,往下继续看 treeifyBin(tab, i) 方法
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
// 如果table.length 小于64 ,那么尝试扩容
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
// 否则,转换成红黑树
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
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
tl.next = p;
tl = p;
}
// 设置table[index] 为当前红黑树头节点
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
2.3 总结点数如何记录
HashMap中的总节点数通过 baseCount 字段和 counterCells 数组字段表示,如下
private transient volatile long baseCount;
private transient volatile CounterCell[] counterCells;
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
通过sumCount方法我们可以知道,ConcurrentHashMap总结点等于baseCount加上counterCells数组中所有有值CounterCell 对象中的value总和,如下图所示,总结点数等于10 + 10 + 2
为什么要这么设计呢,想象一下,如果不使用counterCells数组,直接用baseCount方式来记录总结点数,那么每次put或者remove操作后,每条线程都要自旋修改baseCount,如果是并发冲突比较多的情况下,很多线程都处于自旋等待修改baseCount状态,效率会比较低,增加了counterCells数组记录后,线程通过CAS修改baseCount失败后不是自旋等待,而是立刻尝试随机选中counterCells中的一个下标的记录进行赋值或者修改,如果仍然修改失败才进入自旋去修改baseCount 或者 counterCells,效率有了质的提高
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// CAS对baseCount做加x操作,成功则不执行if代码块内容
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
// ThreadLocalRandom.getProbe() 生成线程随机数
// ThreadLocalRandom.getProbe() & m 定位一个下标
// 如果数组该下标处CounterCell对象不为空,那么通过CAS对象对该对象的value赋值
// 赋值为当前value + x
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 如果if条件为空,那么进入fullAddCount方法自旋增加节点数
fullAddCount(x, uncontended);
return;
}
// check表示是否需要检查扩容
if (check <= 1)
return;
s = sumCount();
}
... 扩容逻辑
}
增添总结点数逻辑主要做了如下几个事情
- CAS 直接把baseCount 设置成baseCount + x,如果失败那么操作counterCells数组
- 通过随机数的方式,随机定位counterCells的一个下标位置,取得该下标位置对应的CounterCell对象a
- 通过CAS技术把a对象的value设置成a + x,如果失败进入fullAddCount方法自旋增加
如果step1 2 3都失败,说明对baseCount 和 counterCells的并发访问冲突存在,那么就自旋修改节点数,fullAddCount 是通过自旋的方式修改总结点数,这个方法用到一个字段叫cellsBusy,cellsBusy是被volatile关键字修饰的,保证它的可见性,当他被设置成1时,只有当前设置cellsBusy为1的线程能对counterCells进行操作,相当于锁的作用
// See LongAdder version for explanation
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 如果分配随机数失败,那么从新init ThreadLocalRandom
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
// collide 为true时会对counterCells数组进行扩容
boolean collide = false; // True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
if ((as = counterCells) != null && (n = as.length) > 0) {
// 通过h随机定位counterCells的一个下标,获取该下标对应的CounterCell对象a
// 如果对象a为空
if ((a = as[(n - 1) & h]) == null) {
// 如果cellsBusy等于0,表示当前counterCells数组没有被别的线程使用
if (cellsBusy == 0) { // Try to attach new Cell
// 新建CounterCell对象,value设置成x
CounterCell r = new CounterCell(x); // Optimistic create
// CAS 设置cellsBusy字段值为1
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
// 重新校验获取该下标对应的CounterCell对象是否为空
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
// 将新建的CounterCell对象r赋值给counterCells[j]
rs[j] = r;
// 创建成功
created = true;
}
} finally {
// 退出时把cellsBusy设置成0
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
// 非冲突
collide = false;
}
// 当addCount方法调用此方法进来的时候,wasUncontended为false
// 说明在addCount方法中CAS设置counterCells时失败
// 应该直接把wasUncontended设置成true,然后直接走到最后
// 通过ThreadLocalRandom.advanceProbe(h)重新生成h值
// 进入下一轮循环中
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// 如果 as[(n - 1) & h]) 不为空,那么CAS赋值a的value为v+x
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
// counterCells != as 那么说明有其他线程扩容了counterCells
// n >= NCPU (cpu 核数)
// 满足上述两个条件那把collide设置成false
else if ( counterCells != as || n >= NCPU)
collide = false; // At max size or stale
// 如果collide为false,那么把collide为true
else if (!collide)
collide = true;
// 如果 collide 为true,那么说明并发访问比较大,对counterCells 数组进行扩容
// 先用CAS 技术 把cellsBusy 设置成1
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
// 以双倍容量新建一个CounterCell类型数组rs,并迁移旧数组的数组到这个新数组
// 最后把这个新数组赋值给counterCells字段
if (counterCells == as) {// Expand table unless stale
CounterCell[] rs = new CounterCell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
// 重新分配Hash
h = ThreadLocalRandom.advanceProbe(h);
}
// 如果counterCells为空,那么初始化counterCells数组
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try { // Initialize table
if (counterCells == as) {
CounterCell[] rs = new CounterCell[2];
rs[h & 1] = new CounterCell(x);
counterCells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
// 直接通过CAS修改baseCount的指为baseCount + x
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
3 扩容
ConcurrentHashMap 是多线程协助进行扩容的,如下所示
图中数字表示table的下标,把每个下标处的位置比作一个桶,那么每个线程负责对一定数量的桶进行扩容操作,每个线程最少负责16个桶,每个线程负责的桶数目是通过计算得出的,如下代码所示
当cpu核数为1时,线程负责处理所有的桶,当大于1时,计算 (n >>> 3) / NCPU 的值, 当这个值小于16时,把每个线程负责的桶设置成16,否则使用算出的数值
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
重温一下每个桶扩容后是如何分布的,在执行put方法时,是通过(n - 1) & hash定位table下标的,hash是通过key的hashCode计算出来的hash,n 是 2 的倍数,假设n = 2的k次方,那么数组下标只与二进制的低k位有关
(n - 1) & hash
假设 n = 32
n 对应的二进制为 00000000000000000000000000100000
n-1对应的二进制位为 00000000000000000000000000011111
(n - 1) & hash 后二进制只有低 5 位有值
扩容后n成倍增长,那么新的n = 64,
n-1 对应二进制为 00000000000000000000000000111111
这里为了方便浏览,把扩容后的n用 nd 表示, nd = 2*n
假设 hash1 为 00000000010000000000000100010001
那么扩容前 (n - 1) & hash 为 00000000000000000000000000010001
扩容前后(nd - 1) & hash 为 00000000000000000000000000010001
假设hash2 为 00000000010000000000000100110001
那么扩容前 (n - 1) & hash 为 00000000000000000000000000010001
扩容后(nd - 1) & hash 为 00000000000000000000000000110001
留意到,如果扩容前,n&hash == 0 , 那么扩容前后 (n - 1) & hash 都相等
如果扩容前, n&hash > 0 , 那么扩容后 (nd - 1) & hash = 扩容前 (n - 1) & hash + n
如下图例子所示,红黑树也类似
简单理解完扩容的逻辑后,我们进入扩容的代码实现中
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
...
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 满足扩容条件,s是总结点数,sizeCtl是扩容阈值s > sizeCtl 时促发扩容
// table 为空,或者table长度已经达到最大时不促发扩容
// while 循环保证同一条线程可以多次参与扩容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
// 扩容结束
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);
}
// 第一次扩容操作
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
这里要特别说明一下 int rs = resizeStamp(n) 的计算操作
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
Integer.numberOfLeadingZeros(n) 方法是计算出n的二进制高位中有多少个连续的0,比如3的二进制中高位有30个0,由此可见,Integer.numberOfLeadingZeros(n) < 32
RESIZE_STAMP_BITS 在类中赋值为16,而且不能修改,那么1 << (RESIZE_STAMP_BITS - 1) 后得到的二进制为
00000000000000001000000000000000,那么resizeStamp计算得出的二进制第16位为1,除了低五位和第16位可能为1外,其余位都为0,而且n的值不同计算出的值也不同
sizeCtl 的值在初始化过程时为-1, 初始化成功后为 0.75 * n,在一开始进入到扩容步骤时,sizeCtl 为 0.75 * n,此时sc = sizeCtl > 0, 在第一条线程进行扩容操作时,会把sizeCtl 设置成rs << RESIZE_STAMP_SHIFT) + 2,由于rs第16位为1,除了低五位可能为1外,其余位都为0,那么rs << RESIZE_STAMP_SHIFT) + 2运算后,最高位为1,那么sizeCtl 绝对是一个负数
所以当sc<0时,表示正在扩容,当sc >0 时,表示第一条线程进行扩容操作,把sizeCtl 设置成rs << RESIZE_STAMP_SHIFT) + 2,非第一条线程进行扩容操作的话每次把sizeCtl 设置成 sc + 1
下面这段代码是对扩容结束的判断
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
当正在进行扩容扩容时sc >>> RESIZE_STAMP_SHIFT可以还原出resizeStamp(n)的值,如果扩容已经结束了,那么sizeCtl会被设置成 新的 n * 0.75,此时sc >>> RESIZE_STAMP_SHIFT 跟resizeStamp(int n)计算出的值不会相等
map中是通过transferIndex这个字段来控制线程负责的桶数的,如果是第一条线程进行扩容操作,那么transferIndex = n,并且当前线程会把transferIndex 设置成( transferIndex - stride), stride 是代码计算出来每个线程应该负责的桶数,当前线程就负责下标为 ( transferIndex - stride)到 transferIndex - 1的桶, 如果不是第一次扩容,那么不用赋值n,直接用transferIndex 计算当前线程应该负责的桶区间
假设 n = 64 ,stride 为16
线程1最先进行扩容操作,此时 transferIndex = 64,线程1负责扩容table下标48 - 63的16个桶,并且设置transferIndex 为 48
线程2为第二条进行扩容操作的线程,此时transferIndex = 48,当前负责的线程负责扩容table下标32 - 47的16个桶,并且设置transferIndex 为 32
… 以此类推
讲了这么多,看看执行扩容的代码逻辑吧
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// 计算每个线程负责的桶数
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
// 如果nextTab为空,表示第一条进入的线程进行扩容操作
// 新建容量为 2*n 的Node数组nextTab
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
// transferIndex 赋值为n
transferIndex = n;
}
// 新数组的长度(2*n)
int nextn = nextTab.length;
// ForwardingNode 类对象的hash值为-1
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
// 表示扩容是否结束
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// 每次for 循环开始advance为true
while (advance) {
int nextIndex, nextBound;
// 如果当前线程正在处理,或者扩容已经结束
if (--i >= bound || finishing)
advance = false;
// transferIndex <0 表示扩容结束
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// transferIndex 设置成nextIndex(transferIndex) - stride
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
// bound = nextBound
// 那么当前线程负责的就是下标bound 到 nextIndex - 1 的桶
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// 如果扩容结束
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 如果finishing为true,表示所有桶处理完成,扩容结束
// 把nextTable赋值给table,并且sizeCtl = 新n * 0.75
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// sizeCtl = sizeCtl - 1
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 如果 (sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT
// 那么表示扩容还未结束
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 设置扩容结束标志
finishing = advance = true;
i = n; // recheck before commit
}
}
// 如果当前桶为空,那么把table[i] 设置成fwd
// 那么下次线程调用put 或者 remove方法时如果发现table[i] 的hash < 0 就协助扩容
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
// 如果当前桶已经被扩容过
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
// 锁住当前桶(链表或者红黑树,进行操作)
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);
// table[i] 设置成fwd
// 那么下次线程调用put 或者 remove方法时如果发现table[i] 的hash <0 就协助扩容
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);
// table[i] 设置成fwd
// 那么下次线程调用put 或者 remove方法时如果发现table[i] 的hash <0 就协助扩容
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
其中对链表和红黑树的扩容拆分操作和HashMap类似
4 get 方法
还记得前文说过,在对某个桶(下标i)扩容操作处理完成后,会把ForwardingNode fwd赋值给table[i],
ForwardingNode类型对象的hash值是-1,当调用get方法,并且计算出key的hash是-1时,表明当前桶已经进行过扩容处理,那么调用ForwardingNode 的find方法,到nextTab数组去寻找,否则,还是在table[i] 进行查找
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
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 &&
// table[i] 节点不为空
(e = tabAt(tab, (n - 1) & h)) != null) {
// 如果table[i] hash 等于h,并且key相等,那么为要找的节点
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 如果hash 小于0, 表明进行过扩容处理,那么调用ForwardingNode 的find方法进行查找
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;
}
这里我有个疑问,在链表转红黑树时虽然也保存了链表结构,但是为什么这里查找的时候没有进行链表还是红黑树的判断,而是直接遍历链表查找?
5 delete方法
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 如果table 为空,或者table[i] 为空,那么直接返回
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
// 如果table[i] 的hash 为 -1,那么协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
// 链表删除
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
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;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
// 总结点数减一,不检查扩容
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}