目录
一、ConcurrentHashMap简介(JDK1.8)
ConcurrentHashMap是一个线程安全的HashMap,之前介绍过Hashtable是线程安全的,但是由于在方法前用了synchronized关键字修饰,所以会导致效率降低。
ConcurrentHashMap使用了CAS和synchronized关键字来保证线程安全。JDK1.7时用的是分段锁实现线程安全,即将一个map分为多个segment对象,操作时锁其中一个segment其他segment对象不受影响。
二、源码分析
1)变量
/**
* 最大容量:2^30
*/
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;
/**
* 当ConcurrentHashMap中元素个数达到64,并且链表长度大于8时才会进行树化
*/
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;
/**
* 用于对 sizeCtl 的位移。
*/
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
/**
* MOVED:当某个节点的hash等于moved时说明数组在扩容,这个桶已经有线程在进行数据迁移的操作
* TTREEBIN: 说明链表已经转换成为红黑树
* HASH_BITS: 计算hash值时用到
*/
static final int MOVED = -1; // hash for forwarding nodes
static final int TREEBIN = -2; // hash for roots of trees
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
/**
* 系统cpu核数
*/
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* 存储节点的数组
*/
transient volatile Node<K,V>[] table;
/**
* 扩容后产生的新数组
*/
private transient volatile Node<K,V>[] nextTable;
/**
* 数组中的元素个数
*/
private transient volatile long baseCount;
/**
* 当sizeCtl = -1时说明数组正在初始化或者扩容
* 当sizeCtl > 0时则代表初始化的大小或者扩容阈值
* 当sizeCtl = -(1 + 扩容线程数): 此时的 sizeCtl 代表的是扩容线程数
*/
private transient volatile int sizeCtl;
/**
* 扩容时用到,代表旧数组的长度
*/
private transient volatile int transferIndex;
/**
* 旋转锁(通过 CAS 锁定)在调整大小或创建计数器单元时使用。
*/
private transient volatile int cellsBusy;
/**
* 计数器单元格表。当为非空时,大小为 2 的幂。
* 多线程添加数据时,可能会导致多个线程同时对baseCount进行CAS操作
* 为了提高效率就设计这个数组当竞争激烈时就用这个代替baseCount
* 获取数组总元素个数时: 总元素个数 = baseCount + counters中的value值。
*/
private transient volatile CounterCell[] counterCells;
2)构造方法
/**
* 无参构造方法,使用此方法生成对ConcurrentHashMap容量为16。
*/
public ConcurrentHashMap() {
}
/**
* 有参构造方法,可以自定义ConcurrentHashMap的容量,但初始容量建议是2的幂次方。
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
/**
* 有参构造方法,将某个map转换为ConcurrentHashMap
*/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
/**
* 有参构造方法,可以自定义ConcurrentHashMap的容量和加载因子
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
/**
* 有参构造方法,可以自定义ConcurrentHashMap的容量和加载因子、并发级别。
*/
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
3)内部类
/**
* 代替baseCount,如果线程对baseCountCAS操作失败则将值修改到这里
*/
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
/**
* 构成链表的存储键值对的节点
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
*/
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
/**
* TreeBin的节点,用于存储数据
*/
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next,
TreeNode<K,V> parent) {
super(hash, key, val, next);
this.parent = parent;
}
Node<K,V> find(int h, Object k) {
return findTreeNode(h, k, null);
}
/**
* 根据key寻找并返回节点,
*/
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
if (k != null) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk; TreeNode<K,V> q;
TreeNode<K,V> pl = p.left, pr = p.right;
// 判断待查找节点在树左边还是右边
if ((ph = p.hash) > h)
p = pl;
else if (ph < h)
p = pr;
// 如果key相等则返回当前节点
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
return p;
// 如果找到最后一个节点还没找到则返回到另一边节点
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
else if ((q = pr.findTreeNode(h, k, kc)) != null)
return q;
else
p = pl;
} while (p != null);
}
return null;
}
}
/**
* 存储红黑树的对象,用于对TreeBin节点添加元素时候的写锁的获取和释放
*/
static final class TreeBin<K,V> extends Node<K,V> {
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
// values for lockState
static final int WRITER = 1; // set while holding write lock
static final int WAITER = 2; // set when waiting for write lock
static final int READER = 4; // increment value for setting read lock
..............
}
4)put方法
1、插入元素时回先判断数组是否初始化
2、如果key对应的位置为空,直接插入元素
3、判断数组是否正在扩容
4、插入元素
/**
* 往map中插入元素
*/
public V put(K key, V value) {
return putVal(key, value, false);
}
/**
*
*/
final V putVal(K key, V value, boolean onlyIfAbsent) {
// ConcurrentHashMap不允许key和value为空
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();
// 如果数组不为空则尝试用CAS往key对应的位置插入元素
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 的位置则说明这个数组正在扩容
else if ((fh = f.hash) == MOVED)
// 当前线程帮助扩容
tab = helpTransfer(tab, f);
// 待插入位置有元素且数组没有在扩容,则synchronized同步代码块插入元素
else {
V oldVal = null;
synchronized (f) {
// 再次判断当前位置的元素是否是刚刚获取的
if (tabAt(tab, i) == f) {
// 判断该位置的节点是属于树节点还是链表节点
// 如果是链表节点则遍历链表进行插入
if (fh >= 0) {
// 计数, HashMap中以0开始是因为树化时判断条件
// binCount > TREEIFY_THRESHOLD - 1
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;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
5)addCount方法
1、对baseCount进行计数操作
2、检查是否需要扩容
/**
* x:插入元素个数或删除元素个数,通常值为1/-1
* check:是否需要进行扩容检查,check >= 0:需要检查,check < 0:不需要检查
*/
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 判断counterCells是否为空,并且尝试使用CAS对baseCount进行修改
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
// 如果计数盒子是空(尚未出现并发)
// 如果随机取余一个数组位置为空 或者
// 修改这个槽位的变量失败(出现并发了)
// 执行 fullAddCount 方法。并结束
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;
}
// 不需要检查是否扩容则直接返回
if (check <= 1)
return;
// 将counterCells的值合并到baseCount中
s = sumCount();
}
// 检查是否需要扩容
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 如果s > sizeCtl并且数组不为空、数组当前容量小于最大容量则进行扩容操作
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 根据length得到一个标识用于后续判断
int rs = resizeStamp(n);
// sc小于零则说明数组正在扩容,当前线程帮助扩容
if (sc < 0) {
// 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
// rs + 1 --> 最少线程数(相当于不正确的情况了,因为起始时最少是 rs + 2)
// 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
// 如果 nextTable == null(结束扩容了)
// 如果 transferIndex <= 0 (转移状态变化了)
// 结束循环
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// 如果可以帮助扩容则将sc + 1,表示多了一个线程在帮助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// 如果不在扩容,将 sc 更新:标识符左移 16 位 然后 + 2. 也就是变成一个负数。高 16 位是标识符,低 16 位初始是 2.
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
// 开始扩容
transfer(tab, null);
s = sumCount();
}
}
}
addCount 方法做了 2 件事情:
-
对 table 的长度加一。无论是通过修改 baseCount,还是通过使用 CounterCell。当 CounterCell 被初始化了,就优先使用他,不再使用 baseCount。
-
检查是否需要扩容,或者是否正在扩容。如果需要扩容,就调用扩容方法,如果正在扩容,就帮助其扩容。
有几个要点注意:
-
第一次调用扩容方法前,sizeCtl 的低 16 位是加 2 的,不是加一。所以 sc == rs + 1 的判断是表示是否完成任务了。因为完成扩容后,sizeCtl == rs + 1。
-
扩容线程最大数量是 65535,是由于低 16 位的位数限制。
-
这里也是可以帮助扩容的,类似 helpTransfer 方法。
参考资料:
并发编程——ConcurrentHashMap#addCount() 分析_concurrenthashmap的addcount_仰望星空的尘埃的博客-CSDN博客
6)sumCount方法、fullAddCount方法
参考资料:ConcurrentHashMap 的 addCount 和 fullAddCount 阅读 - 掘金 (juejin.cn)
7)transfer方法
/**
* 数组扩容中的数据迁移操作
*
*/
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//当数据数量大于16时,数据迁移的工作是将数组分段然后多线程完成的 这里主要是判断用几个线程来完成迁移操作 默认一个线程迁移16个元素
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//创建新数组 存储数据
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;
//迁移下标 表示迁移进行到哪个桶 迁移是从右到左的所以将原先map的大小赋值给transferIndex
transferIndex = n;
}
int nextn = nextTab.length;
//新建一个占位对象,该占位对象的 hash 值为 -1 该占位对象存在时表示集合正在扩容状态,key、value、next 属性均为 null ,nextTable 属性指向扩容后的数组
//该占位对象主要有两个用途:
// 1、占位作用,用于标识数组该位置的桶已经迁移完毕,处于扩容中的状态。
// 2、作为一个转发的作用,扩容期间如果遇到查询操作,遇到转发节点,会把该查询操作转发到新的数组上去,不会阻塞查询操作。
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;
//这个循环用于处理一个 stride 长度的任务,i 后面会被赋值为该 stride 内最大的下标,而 bound 后面会被赋值为该 stride 内最小的下标
//通过循环不断减小 i 的值,从右往左依次迁移桶上面的数据,直到 i 小于 bound 时结束该次长度为 stride 的迁移任务
//结束这次的任务后会通过外层 addCount、helpTransfer、tryPresize 方法的 while 循环达到继续领取其他任务的效果
while (advance) {
int nextIndex, nextBound;
//每处理完一个hash桶就将 bound 进行减 1 操作
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
//transferIndex <= 0 说明数组的hash桶已被线程分配完毕,没有了待分配的hash桶,将 i 设置为 -1 ,后面的代码根据这个数值退出当前线的扩容操作
i = -1;
advance = false;
}
//只有首次进入for循环才会进入这个判断里面去,设置 bound 和 i 的值,也就是领取到的迁移任务的数组区间
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
//从后开始往前遍历元素 判断i是否小于0或者超出 扩容前 的大小,i + n是否超过 扩容后 的大小
// 为什么是i+n? 迁移机制
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//扩容结束后做后续工作,将 nextTable 设置为 null,表示扩容已结束,将 table 指向新数组,sizeCtl 设置为扩容阈值
if (finishing) {
nextTable = null;
table = nextTab;
//调整扩容阈值
sizeCtl = (n << 1) - (n >>> 1);
return;
}
//每当一条线程扩容结束就会更新一次 sizeCtl 的值,进行减 1 操作
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//(sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT 成立,说明该线程不是扩容大军里面的最后一条线程,直接return回到上层for循环
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
//(sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT 说明这条线程是最后一条扩容线程
//之所以能用这个来判断是否是最后一条线程,因为第一条扩容线程进行了如下操作:
// U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)
//除了修改结束标识之外,还得设置 i = n; 以便重新检查一遍数组,防止有遗漏未成功迁移的桶
finishing = advance = true;
i = n; // recheck before commit
}
}
//遇到数组上空的位置直接放置一个占位对象,以便查询操作的转发和标识当前处于扩容状态
//如果i所处位置为空则直接插入 因为发生扩容时原来的map不一定是满的
//发生扩容的条件 1、发生hash冲突 2、容量达到扩容阈值
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
// 数组上遇到hash值为MOVED,也就是 -1 的位置,说明该位置已经被其他线程迁移过了,
// 将 advance 设置为 true ,以便继续往下一个桶检查并进行迁移操作
advance = true; // already processed
else {
//开始数据迁移
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
//按链表的方式进行插入
if (fh >= 0) {
//将需要插入的节点的hash与原先容量大小进行与运算
int runBit = fh & n;
//lastRun是最后一段fh&n不变的链表
Node<K,V> lastRun = f;
//找出最后一段完整的fh&n不变的链表,这样最后这一段链表就不用重新创建新结点了。
for (Node<K,V> p = f.next; p != null; p = p.next) {
//将每个节点与原先容量大小进行与运算
int b = p.hash & n;
//
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
//根据 lastRun 节点的高位标识(0 或 1),首先将 lastRun设置为 ln 或者 hn 链的末尾部分节点,后续的节点使用头插法拼接
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
//循环遍历桶内节点 将节点分为两种
//1、需要迁移的节点 2、不需要迁移的节点
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
//如果当前节点的hash和n进行与运算结果为零 则说明这个节点不需要迁移
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
//反之则是需要迁移的节点
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
//setTabAt方法调用的是 Unsafe 类的 putObjectVolatile 方法
//使用 volatile 方式的 putObjectVolatile 方法,能够将数据直接更新回主内存,并使得其他线程工作内存的对应变量失效,达到各线程数据及时同步的效果
//使用 volatile 的方式将 ln 链设置到新数组下标为 i 的位置上
setTabAt(nextTab, i, ln);
//使用 volatile 的方式将 hn 链设置到新数组下标为 i + n(n为原数组长度) 的位置上
setTabAt(nextTab, i + n, hn);
//迁移完成后使用 volatile 的方式将占位对象设置到该 hash 桶上,该占位对象的用途是标识该hash桶已被处理过,以及查询请求的转发作用
setTabAt(tab, i, fwd);
//advance 设置为 true 表示当前 hash 桶已处理完,可以继续处理下一个 hash 桶
advance = true;
}
//按树的方式进行插入
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
//lo 为低位链表头结点,loTail 为低位链表尾结点,hi 和 hiTail 为高位链表头尾结点
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
//同样也是使用高位和低位两条链表进行迁移
//使用for循环以链表方式遍历整棵红黑树,将书中节点分为需要迁移到新下标的和不需要迁移到新下标的高位和低位两条链表,使用尾插法拼接 ln 和 hn 链表
for (Node<K,V> e = t.first; e != null; e = e.next) {
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
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
//形成中间链表后会先判断是否需要转换为红黑树:
//1、如果符合条件则直接将 TreeNode 链表转为红黑树,再设置到新数组中去
//2、如果不符合条件则将 TreeNode 转换为普通的 Node 节点,再将该普通链表设置到新数组中去
//(hc != 0) ? new TreeBin<K,V>(lo) : t 这行代码的用意在于,如果原来的红黑树没有被拆分成两份,那么迁移后它依旧是红黑树,可以直接使用原来的 TreeBin 对象
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方法调用的是 Unsafe 类的 putObjectVolatile 方法
//使用 volatile 方式的 putObjectVolatile 方法,能够将数据直接更新回主内存,并使得其他线程工作内存的对应变量失效,达到各线程数据及时同步的效果
//使用 volatile 的方式将 ln 链设置到新数组下标为 i 的位置上
setTabAt(nextTab, i, ln);
//使用 volatile 的方式将 hn 链设置到新数组下标为 i + n(n为原数组长度) 的位置上
setTabAt(nextTab, i + n, hn);
//迁移完成后使用 volatile 的方式将占位对象设置到该 hash 桶上,该占位对象的用途是标识该hash桶已被处理过,以及查询请求的转发作用
setTabAt(tab, i, fwd);
//advance 设置为 true 表示当前 hash 桶已处理完,可以继续处理下一个 hash 桶
advance = true;
}
}
}
}
}
}