ConcurrentHashMap源码解析
HashMap存在的问题:
HashMap线程不安全
因为多线程环境下,虽然1.8改进后put操作在扩容时避免引起死循环,但依旧会存在数据覆盖的情况,所以在并发情况下不能使用HashMap。
Hashtable线程安全但效率低下
Hashtable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问Hashtable的同步方法时,其他线程访问Hashtable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的实现——JDK8版本
在JDK1.7之前,ConcurrentHashMap是通过分段锁机制来实现的,所以其最大并发度受Segment的个数限制。因此,在JDK1.8中,ConcurrentHashMap的实现原理摒弃了这种设计,而是选择了与HashMap类似的数组+链表+红黑树的方式实现,而加锁则采用CAS和synchronized实现。
CAS原理
一般地,锁分为悲观锁和乐观锁:悲观锁认为对于同一个数据的并发操作,一定是为发生修改的;而乐观锁则任务对于同一个数据的并发操作是不会发生修改的,在更新数据时会采用尝试更新不断重试的方式更新数据。
CAS(Compare And Swap,比较交换):CAS有三个操作数,内存值V、预期值A、要修改的新值B,当且仅当A和V相等时才会将V修改为B,否则什么都不做。Java中CAS操作通过JNI本地方法实现,在JVM中程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(Lock Cmpxchg);反之,如果程序是在单处理器上运行,就省略lock前缀。
Intel的手册对lock前缀的说明如下:
确保对内存的读-改-写操作原子执行。之前采用锁定总线的方式,但开销很大;后来改用缓存锁定来保证指令执行的原子性。
禁止该指令与之前和之后的读和写指令重排序。
把写缓冲区中的所有数据刷新到内存中。
CAS同时具有volatile读和volatile写的内存语义。
不过CAS操作也存在一些缺点:1. 存在ABA问题,其解决思路是使用版本号;2. 循环时间长,开销大;3. 只能保证一个共享变量的原子操作。
put操作
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) {
if (key == null || value == null) throw new NullPointerException();
//计算hash值
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();
/**
* 若当前桶为null,则尝试cas插入
* 插入成功:正常 break 走addCount逻辑(size+1和判断是否需要扩容)
* 插入失败:可能会走到最后一个else逻辑中
*/
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
}
/**
* 当前节点的hash = MOVED = -1 则说明数组可能是正在处于扩容阶段,当前节点为ForwardingNode(需要结合扩容逻辑理解)
* 当前线程帮助扩容
*/
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
/**
* 存在hash碰撞,直接锁住该桶
*/
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;
/**
* 存在 key 相同情况 (直接覆盖并返回旧值)
*/
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;
}
}
}
}
/**
* binCount 可以理解为当前桶的链表长度
* 当 binCount = TREEIFY_THRESHOLD = 8 ,则进行红黑树的转换
*/
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
/**
* 这个方法包括 size+1 和 判断是否需要扩容 的逻辑
*/
addCount(1L, binCount);
return null;
}
该方法与jdk1.8的HashMap的put方法逻辑类似,分为几种情况:
① 若数组没有初始化时,则尝试初始化再寻找一个合适地方添加node,而size+1和扩容逻辑在addCount方法中
② 若数组正在处于扩容阶段,也就时该Node节点的hash = -1,就会调用helpTransfer这个方法进行帮助扩容,
扩容完成后继续后续操作
initTable初始化方法
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
//若sc为负数则说明数组正在被初始化,当前线程放弃CPU资源等待一会儿
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
//只有一个线程能cas成功,并将sc修改成-1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
/**
* 二次校验,若sc确实为负数则说明正在创建
* DEFAULT_CAPACITY = 16
*/
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;
// n >>>(无符号右移) 2 = n / 2的平方 = n / 4
sc = n - (n >>> 2);
}
} finally {
/**
* 当 n = 16 ,sizeCtl = 16 - 4 = 12
*/
sizeCtl = sc;
}
break;
}
}
return tab;
}
该方法逻辑也很直白:cas成功的线程进行扩容,再将扩容的阈值赋给sizeCtl,失败的线程调用yield方法释放CPU资源等待完成
addCount计算size和扩容逻辑
private final void addCount(long x, int check) {
/**
* 在put情况下 x=1,check = 该桶链表长度
* 在remove情况下 x=-1 ,check = -1
*/
CounterCell[] as; long b, s;
/**
* 这个if 用于计算size
* 可能会存在这么几种情况
* ① counterCells == null || cas修改 baseCount 成功 ,该 if 逻辑结束
* ② counterCells == null || cas修改 baseCount 失败 ,进入 if 逻辑
* ③ counterCells != null 后面逻辑直接跳过 ,进入 if 逻辑
*/
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
/**
* 添加情况与删除情况类似,这里只分析一种情况
* 添加情况:
* 第二种情况:counterCells == null || cas修改 baseCount 失败
* as为局部变量依旧为null 直接进入 fullAddCount(x = 1 , uncontended = true)
* 第三种情况:counterCells != null
* 说明counterCells已经初始化,则前面两个判断肯定false,
* 第三个判断条件为 as[ThreadLocalRandom.getProbe() & m]) == null ,
*
* 若成立 进入 if分支 fullAddCount(x = 1 , uncontended = true)
*
* 若不成立 则通过cas修改 CELLVALUE +1 ,成功该 if 分支结束 ,失败则进入
* if 分支 fullAddCount(x = 1 , uncontended = false)
*
*
* 补充:ThreadLocalRandom.getProbe() 在未被初始化之前无论调用多少次都是 0,
* 在没调用ThreadLocalRandom.advanceProbe(h)之前 无论调用多少次都是固定值
* ThreadLocalRandom.localInit() 为初始化
*/
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
/**
* 修该该索引位置元素的value成功并且链表长度<=1 直接返回
* 可能put时,该桶链表长度小于等于1
* 可能remove时,check = -1 不用走扩容条件
*/
if (check <= 1)
return;
//统计size (baseCount + counterCells里所有value的总和)
s = sumCount();
}
//---------------------------------------------------------------------
//尝试扩容
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
/**
* 假设为默认容量 16 ,sizeCtl 为 16 - 4 = 12
* 若 s > 阈值 则进行扩容
*/
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
/**
* RESIZE_STAMP_BITS = 16
* Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
* 暂时理解为 得到一个特别大的正整数
*/
int rs = resizeStamp(n);
// sc<0 则说明正在扩容阶段,则cas修改成功则参与扩容
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);
}
//(尝试成为第一个扩容的线程)cas将sc修改为一个特别大的负数,修改成功则进行扩容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
//重新计算是否需要二次扩容
s = sumCount();
}
}
}
该方法分为两部分:
第一个if分支用于控制size的计算
第二个if分支根据前面的size判断是否需要扩容
fullAddCount竞争baseCount失败后的兜底方案
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 初始化 ,并修改 wasUncontended = true
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
/**
* 假设此时 counterCells 还未被初始化 ,则会走第二个逻辑:cas修改cellsBusy为 1
* 创建一个 长度为 2 的 CounterCell
* 假设此时 counterCells 已经被初始化,则会进入第一个 if 分支
*/
if ((as = counterCells) != null && (n = as.length) > 0) {
/**
* ① as[(n - 1) & h] 为 null 且 cas修改cellsBusy 为 1 成功,则直接创建一个 CounterCell
* 并赋值给该索引位置
* ② as[(n - 1) & h] 为 null 且 cas修改cellsBusy 为 1 失败,重新进入最外层 if 分支
*/
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
CounterCell r = new CounterCell(x); // Optimistic create
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
//在加锁情况下再次判断(代码严谨性)
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
/**
* 只有在外部方法外部尝试cas修改失败传进来的wasUncontended为false 则会进入该分支然后rehash
*/
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
/**
* cas修改成功直接退出循环 = 退出方法
*/
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
/**
* counterCells != as 成立,则说明正在扩容
* NCPU = CPU 核数
* 通过第二个判断条件可以推断出 counterCells 最多不会超过CPU核数的2次幂
*/
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
/**
* 在这个分支判断时 collide 在绝大部分情况都为false ,也就是进入分支修改 collide为true
* 再rehash
*
* 只有在进行过rehash之后,该索引位置元素依旧不为null 且,cas修改失败,这时 collide 为
* false 进入下一个分支 进行尝试扩容
*/
else if (!collide)
collide = true;
//扩容逻辑 容量*2
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
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);
}
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
//此if分支最后一个判断为cas则说明只有一个线程能进入
boolean init = false;
try { // Initialize table
if (counterCells == as) {
CounterCell[] rs = new CounterCell[2];
/**
* 初始化该 h & 1 位置上的 CounterCell 为 1
* CounterCell 类上面有一个 @sun.misc.Contended 注解 (避免伪共享)
* https://www.jianshu.com/p/c3c108c3dcfd
*/
rs[h & 1] = new CounterCell(x);
counterCells = rs;
init = true;
}
} finally {
//将 cellsBusy 还原
cellsBusy = 0;
}
// init为 true直接 break
if (init)
break;
}
/**
* 竞争cellbusy失败时才会进入此兜底方法,竞争bascount
*/
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
这个方法分为四个部分:
① 若counterCells未被初始化则创建一个长度为2的CountCell数组,并在 h & 1位置上创建一个值为x的对象代表size+1成功
② 若counterCells已经被初始化则会生成一个随机数并hash出一个索引,通过cas修改该位置上元素的value+1
③ 若第二步cas失败则会继续生成一个随机数,重复第二步,依旧失败。此时则会尝试扩容
④ 其余极端情况则会竞争basecount
sumCount统计size
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;
}
该方法逻辑很简单:以basecount为基数,依次加countcell的value值
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
/**
* 计算步长
* 该 if 判断可以理解为 stride = (n / 8) < 16 ? 16 : (n / 8)
*/
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//初始一个2倍大小的 tab 并赋值给 nextTable
// transferIndex = oldtab的长度
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;
}
int nextn = nextTab.length;
//构建一个hash 为 -1 的特殊 node
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//该变量为false说明区间分配完成
boolean advance = true;
//该变量为true说明被分配的区间任务完成
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
/**
* 假设第一次进入该循环
* stride = 16
* transferIndex = 16
* --i = -1
* bound = 0
* 两个判断都为false,则会进入第二个分支
* (nextIndex = 16 <= 0)依旧不成立,
* 进入第三个分支 cas修改 transferIndex 为
* nextBound = (nextIndex > stride ?nextIndex - stride : 0)) = 0
* bound = 0
* i = 15
* advance = false 则退出循环
*
* 通过走一遍流程可以此时可以大致将这个循环的作用就是计算该线程被分配的 区间
*/
if (--i >= bound || finishing)
advance = false;
//当 transferIndex <= 0 说明该所有区间都已经被分配
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
//根据步长依次递减transferIndex
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
/**
* 当 i < 0 成立则说明 该线程所分配的区间任务完成
*/
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
/**
* 若该索引位置为null 直接放置一个占位符 ,与开头的putVal()里面的第三个 if 分支相互呼应,
* 其他分支看到该占位符则会帮助扩容
*/
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
/**
* 若该线程碰到了占位符 则说明该区间有线程正在处理中,继续下一个节点(从末尾向前寻找)
*/
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
/**
* 锁住该桶 进行扩容,由于不存在并发就不仔细分析了
* 大致逻辑就是就是依次遍历当前桶中每个节点,最终会得到两个链表,其中一个放置在
* 新数组的 i 索引位置 ,另一个放置在 i+n 索引位置 ,最后将老数组 i 索引位置修改成占位符
*/
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;
}
}
}
}
}
}
transferkuo扩容逻辑
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
/**
* 计算步长
* 可以理解为 stride = (n / 8) < 16 ? 16 : (n / 8)
*/
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//初始一个2倍大小的 tab 并赋值给 nextTable
// transferIndex = oldtab的长度
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;
}
int nextn = nextTab.length;
//构建一个hash 为 -1 的特殊 node
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//该变量为false说明区间分配完成
boolean advance = true;
//该变量为true说明被分配的区间任务完成
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
/**
* 假设第一次进入该循环
* stride = 16
* transferIndex = 16
* --i = -1
* bound = 0
* 两个判断都为false,则会进入第二个分支
* (nextIndex = 16 <= 0)依旧不成立,
* 进入第三个分支 cas修改 transferIndex 为
* nextBound = (nextIndex > stride ?nextIndex - stride : 0)) = 0
* bound = 0
* i = 15
* advance = false 则退出循环
*
* 通过走一遍流程可以此时可以大致将这个循环的作用就是计算该线程被分配的 区间
*/
if (--i >= bound || finishing)
advance = false;
//当 transferIndex <= 0 说明该所有区间都已经被分配
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
//根据步长依次递减transferIndex
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
/**
* 当 i < 0 成立则说明 该线程所分配的区间任务完成
*/
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//若这个条件成立则说明扩容全部完成
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
/**
* 若该索引位置为null 直接放置一个占位符 ,与开头的putVal()里面的第三个 if 分支相互呼应,
* 其他分支看到该占位符则会帮助扩容
*/
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
/**
* 若该线程碰到了占位符 则说明该区间有线程正在处理中,继续下一个节点(从末尾向前寻找)
*/
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
/**
* 锁住该桶 进行扩容,由于不存在并发就不仔细分析了
* 大致逻辑就是就是依次遍历当前桶中每个节点,最终会得到两个链表,其中一个放置在
* 新数组的 i 索引位置 ,另一个放置在 i+n 索引位置 ,最后将老数组 i 索引位置修改成占位符
*/
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;
}
}
}
}
}
}
可以看出这个方法的逻辑比较复杂,涉及到很多种情况,但是目的比较明确:
首先每个进入方法的线程尝试分配一个区间,当分配成功后则从后向前依次锁定该桶进行数据搬运,而搬运逻辑就是构建出两个链表ln和hn,分别放置在新数组的 i 和 i+n(n=老数组长度) 索引位置,将老数组 i 索引位置上放上占位标记。

浙公网安备 33010602011771号