1、putVal()
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();//检查
int hash = spread(key.hashCode()); //计算key的hash值
int binCount = 0;//记录哈希桶节点的个数
for (Node<K,V>[] tab = table;;) { //tab赋值
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0) //tab == null且length == 0,初始化哈希表 注意这里n也赋值了length
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //按位与运算后给f赋值hash槽位 如果槽位为空
if (casTabAt(tab, i, null, //cas的方式直接插入新节点
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED) //MOVED 表示正在扩容
tab = helpTransfer(tab, f); //帮助扩容迁移
else {
V oldVal = null;
synchronized (f) { //加锁对象是f f只是对应一个下标的哈希槽 锁的范围很小
if (tabAt(tab, i) == f) { //再检查一下
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) { //遍历链表 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) //bincount >= 8 树化
treeifyBin(tab, i);
if (oldVal != null)
return oldVal; //是更新操作则返回旧值
break;
}
}
}
addCount(1L, binCount); //更新计数器、检查扩容
return null; //非更新操作 返回null
}
2、addCount() 累加计数器和检查扩容
- 我们以元素个数为11个,表长度为16为例。也就是说sizeCtl = 12
private final void addCount(long x, int check) { //x是1,check是链表长度 但好像不是很重要
CounterCell[] as; long b, s;
//无竞争状态下counterCells == null 且 CAS baseCount = 11 + 1(累加1)成功,不会进入下面代码块
//注意s也赋值了,变成了11+1=12,下面检查扩容会用
//有竞争进入则进入以下代码块
//内部实现后面再说,是longaddr的思想
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m; //CAS失败、存在竞争
boolean uncontended = true;
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;
s = sumCount();// 更新计数器的总和
}
if (check >= 0) { //检查扩容
Node<K,V>[] tab, nt; int n, sc;
//如果元素个数大于等于sizeCtl,需要扩容。此时12>=12
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//计算扩容戳,并左移16位
int rs = resizeStamp(n) << RESIZE_STAMP_SHIFT;
// 如果 sizeCtl 小于 0,表示当前正在进行扩容操作
// 第一次尝试扩容是不会进入下面这个if的
if (sc < 0) {
// 为负数时,低位储存扩容的线程数
// 如果sc == 扩容戳 + 最大扩容线程,表示扩容的线程数已经到最大值了。
// sc == rs + 1,表示扩容已经结束了,但正在进行重新检查原table的善后阶段,为什么这么说,带着这个问题去读接下来的源码
// nextTable表示扩容用的新table,如果它为空表示没有在进行扩容或者已经扩容结束
// transferIndex表示扩容索引,如果小于等于0表示没有在进行扩容操作或扩容结束
// 满足四种情况之一会break
if (sc == rs + MAX_RESIZERS || sc == rs + 1 ||
(nt = nextTable) == null || transferIndex <= 0)
break;
// 尝试增加扩容线程,sizectl低位加1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// 不是正在扩容的情况下走到这里,表示当前没有进行扩容操作,尝试发起扩容操作
// CAS sizectl CAS成功后变成负数 表示正在扩容
// sizectl 变成 rs+2 ,rs是扩容戳,为什么是rs+2而非rs+1,带着这个问题去读接下来的源码
else if (U.compareAndSwapInt(this, SIZECTL, sc, rs + 2))
transfer(tab, null); //扩容
s = sumCount();// 更新计数器的总和
}
}
}
3、transfer() 计算步长
- 注:代码是连续的,但是我们根据每段代码的作用分段看
//NCPU表示系统当前可用的处理器数量 这里以16为例
static final int NCPU = Runtime.getRuntime().availableProcessors();
//注意参数,nextTab表示扩容用的新table,第一次扩容nextTab是null
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride; //我们的n = 16
//这里的步长可以理解为每次迁移的范围
//最小步长MIN_TRANSFER_STRIDE = 16
//可以自己分别代入一个NCPU比较大的数字,或者代入一个n(也就是原table长度)比较大的数字
//处理器数量较小或table较小的时候stride基本等于最小步长
//处理器数量多了,table大后步长会多一点。
//如果处理器数量只有一个的话,步长就是table长度,也就是单线程情况下一次迁移完整个table
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
}
4、transfer() 初始化nextTable(针对第一个来扩容的线程)
if (nextTab == null) { // initiating 初始化nextTab
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //新table的长度是原table长度 * 2
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
//这两个成员变量都是被volatile修饰的,保证不同线程操作扩容时的可见性
nextTable = nextTab;
transferIndex = n;//用于控制迁移的位置 这里n = 16
}
5、transfer() 分配任务
- 先看看ForWardingNode
ForwardingNode(Node<K,V>[] tab) {
super(MOVED, null, null, null);
this.nextTable = tab;
}
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
this.hash被赋值成了MOVED
,表示已经迁移,MOVED就是-1,后面会见到这个MOVED
int nextn = nextTab.length; //新table长度 这里是32
//当扩容过程中遇到需要移动的节点时,可以将这些节点替换为 ForwardingNode,从而标识该位置正在进行扩容操作
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;;) { //初始时i = 0 bound = 0 finish = false
Node<K,V> f; int fh;
while (advance) { //寻找此次迁移任务的边界
int nextIndex, nextBound;
// 第一次不会进入这个if块
// 看开始迁移的代码,遇到advance变回true再看这个if块,他是控制迁移的索引的
if (--i >= bound || finishing)
// 如果 i 大于等于 bound ,迁移任务还没结束,advance重新变为false 继续走迁移的逻辑
advance = false;
//初始时transferIndex = 16 ,也不会进入这里,但注意nextIndex被赋值了
else if ((nextIndex = transferIndex) <= 0) {
// 扩容结束
i = -1;
advance = false;
}
// 第一次进入的是这里
// 由TRANSFERINDEX直接控制迁移任务的工作区间
// 本次的示例是table长为16,transferIndex = 16。如果是32的话分配的就是[16,31]了,另一个线程进来分到[0,15]
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ? // 16 > 16 ? 16 - 16 : 0,这里nextBound为0
nextIndex - stride : 0))) {
bound = nextBound; //bound赋值
i = nextIndex - 1; //i = 15
advance = false;
}
}
- 第一次分配结束 bound = 0,i = 15,所以此次迁移任务的区间就是[0,15]
6、transfer() 开始迁移
// 如果i < 0 分俩种情况
// 1. 表示迁移结束,初始时i = 0,(上一块代码的if块)在第一个if块自减为-1,但没有进入最后一个if块分配到迁移区间,这时i仍然为-1
// 2.
// i >= n n是原数组长度,迁移任务区间超出了table范围,不会成立
// i + n >= nextn nextn是新table的长度,i的最大值就是原数组最大索引,也不可能成立
// 所以我们先看后面的if块,这里的if块一会讲
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
}
}
// 如果原table的索引i处为null,将此处替换为fwd表示已经迁移(如果不为null会走下面synchronized里的逻辑)
else if ((f = tabAt(tab, i)) == null) //==null
// 替换成功则advance为true,表示一个索引处迁移结束
// advance为true后就又可以进入分配任务的while循环中了
// 再去看看分配任务的第一个if块,通过--i >= bound控制下一次迁移索引
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED) //MOVED 表示此索引处的元素已经被迁移
//同样的advance变为true,进行i的自减
advance = true; // already processed
else {
//数据不为null情况下的迁移逻辑,和hashmap差不多,不说了,我们只关注迁移后的advance = true
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;
}
}
}
}
7、迁移结束的善后处理以及rs + 2 & sc + 1的解释
- 当i = -1后,会进入迁移结束的if块
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//最开始进来finish仍然为false
//善后工作
if (finishing) {
nextTable = null;
table = nextTab; //更新table
sizeCtl = (n << 1) - (n >>> 1); //计算新的阈值
return;
}
//毫无疑问会先执行这个CAS,他将sizeCtl减了1,sizeCtl低位表示扩容的线程数量,这表示一个线程结束了它的迁移扩容任务
//还记得在addCount中有一个sc == rs + 1的判断吗,以及为什么最开始sc = rs + 2 而非 rs + 1
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//情况:如果 (sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT,表示sizeCtl被操作过了,迁移过程中有别的线程也来帮助迁移了。
//可以思考一下,最终更新table和sizectl的操作应该由哪个线程进行,显然应该是执行最后一个迁移任务的线程
//别的线程迁移结束后纷纷将sizeCtl - 1,最后一个线程sizeCtl - 2刚好就是迁移戳resizeStamp(n) << RESIZE_STAMP_SHIFT
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return; //非最后一个线程,直接return
//最后一个线程才会进入这里,处理善后工作
finishing = advance = true;
i = n; // recheck before commit // i = 原数组长度,重新走--i>=bound的逻辑,重新检查一遍
// 这里的重新检查实际上是判断原table是不是全部都是MOVED了,如果不是则再迁移一次
// 检查完后才进入if(finish)中
}
}
8、helpTransfer()
在putVal操作时有时会遇到访问的下标正在扩容的情况
else if ((fh = f.hash) == MOVED) //MOVED 表示正在扩容
tab = helpTransfer(tab, f); //帮助扩容迁移
- 前面看懂了这块代码应该很容易理解了
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// 检查条件:tab 不为空,f 是 ForwardingNode,并且下一个表不为空
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
// 计算扩容戳
int rs = resizeStamp(tab.length) << RESIZE_STAMP_SHIFT;
// 二次检查处于扩容状态
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
//如果扩容线程已满或扩容已经进入善后阶段,不扩容
if (sc == rs + MAX_RESIZERS || sc == rs + 1 ||
transferIndex <= 0)
break;
//低位+1,一起扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
9、扩容时机2
- 长链表时扩容
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD) //bincount >= 8 树化
treeifyBin(tab, i);
if (oldVal != null)
return oldVal; //是更新操作则返回旧值
break;
}
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
// 如果哈希表的容量小于树化阈值
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;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
10、tryPresize()
private final void tryPresize(int size) {
// tableSizeFor() 找size的最近二次幂
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
// 这块代码是针对putAll的,大体就是根据传进来的size初始化了一个size大小的table,这里不说了
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
//主要看这里
else if (tab == table) {
//还是先计算扩容戳
int rs = resizeStamp(n);
if (sc < 0) { //表示已经是扩容状态了
Node<K,V>[] nt;
//和addCount()里的判断差不多,检查是不是善后状态是不是已经最大线程了什么的
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//sizeCtl低为加1,然后开始transfer()
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); //扩容
}
}
}
先未完待续吧,但也差不多了,东西实在是太多了