1. 查询get方法
1.1 tab位置如何定位
1.1.1 tableSizeFor方法:
在初始化时将tab长度n限制为2的N次方,图1(右)以n=41为例展示tableSizeFor方法的执行过程。
tableSizeFor方法private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
1.1.2 定位 hash&(n-1):
为了hash值均匀分布。
n为2的N次方,n-1则是一个二进制低位全为1的数字,在做‘与’操作时,可以减小tab中索引位置冲突概率,比如:
n=16时,n-1=15, 15的二进制为00001111,15&3(00000011)=3、15&1(00000001)=1
如果n=14时,n-1=13, 13的二进制为00001101,13&3(00000011)=1、13&1(00000001)=1,1和3就会有冲突落在一个tab槽中,同理5、7 也会分别落在同一个槽中,这样hash冲突概率就会增大。
1.2 get方法(图1 左)
图1
1.2.1 扩容时查询
扩容时,旧tab的node转为forwarddingNode, hash=-1;入时,synchronized保证线程安全;查询时判断node的hash值为-1,则走ForwardingNode的find方法查询,走的是新的tab查询。fwd节点是在transfer方法中替换的。
ForwardingNode类代码static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);//MOVED常量-1
this.nextTable = tab;
}
Node<K,V> find(int h, Object k) {
// loop to avoid arbitrarily deep recursion on forwarding nodes
outer: for (Node<K,V>[] tab = nextTable;;) {
Node<K,V> e; int n;
if (k == null || tab == null || (n = tab.length) == 0 ||
(e = tabAt(tab, (n - 1) & h)) == null)
return null;
for (;;) {
int eh; K ek;
if ((eh = e.hash) == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
if (eh < 0) {
if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable;//e节点还在扩容中,继续讲tab复制到下一层tab,跳回outer执行
continue outer;
}
else
return e.find(h, k);//e为普通链接节点,去查询链表中
}
if ((e = e.next) == null)
return null;
}
}
}
}
2. putVal方法
2.1 put方法流程见图2(上)
2.1.1 注意点
- tab数组在第一次put时初始化;
- 在操作节点时,CAS+synchronized锁头节点避免线程安全问题;
- key存在时替换value值,不存在时新增节点并且触发addCount方法;
- 链表数达到阈值(链表数量和tab长度两个维度限制)触发树化操作。
2.1.2 扩容时修改
触发helpTransfer方法帮助扩容,完成之后返回循环继续put节点。
图2
putVal方法代码final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
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();
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 头节点为null时不加锁,只有一步操作用CAS和volatile即可保证线程安全
}
else if ((fh = f.hash) == MOVED)//扩容中,帮助扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {//synchronized锁保证线程安全
if (tabAt(tab, i) == f) {
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;
}
}
}
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;
}
2.2 addCount方法流程见图2(下)
2.2.1 计数方式
ConcurrentHashMap计数时使用BASECOUNT+countCells,分步计数。有效提高多线程扩容时的效率。
2.2.2 sizeCtl参数
初始化时-1、扩容时(高16位扩容戳+(低16位线程数+1))、扩容完成后为下一次扩容阈值
addCount方法代码private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||// BASECOUNT增加数量失败后,随机选一个CounterCell数组中的元素增加数量
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
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);// 扩容中,将SIZECTL加1表示扩容线程+1
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);// 首次扩容,将SIZECTL=扩容时间戳<<RESIZE_STAMP_SHIFT+2(扩容线程数+1)
s = sumCount();
}
}
}
3. transfer方法
3.1 注意点
- 扩容时,多个线程分段迁移数据,使用i、bound参数分段。
- 迁移数据时,使用synchronized加锁头节点避免多线程问题
- 高位链、低位链方法完成快速迁移(见图3 右)
- 红黑树节点也是一个单向链表,迁移时同样使用高低链形式,迁移完需要判断红黑树节点是否需要链化。
图3(高低链示例图,当n=16时,示例图中写的“hash&7”是错误的,应该是“hash&15”)
transfer方法代码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 stride是数据迁移时分段长度,根据cpu数量查询,最小为16
if (nextTab == null) { // initiating nextTab为空时初始化
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初始值为tab长度
}
int nextn = nextTab.length;
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;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {//nextBound赋值,n=32为例:nextIndex=32、stride=16 =》第一次:nextBound=16、i=31;第二次nextBound=0、i=15。所以每个线程以16个长度为一段操作。
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);//全部扩容完成,sizeCtl设置为新容量的0.75倍
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
}
}
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);
setTabAt(tab, i, fwd);//设置原tab节点位置为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;
}
}
}
}
}
}
4. 使用场景(相比HashMap)
- value是volatile修饰,改动时及时查询
- putVal时判断tab[i]是否等于null,配合volatile防止覆盖
- 防止并发多次扩容
- putIfAbsence方法,保证不覆盖