6_分解构造方法源码
/* ---------------- Public operations -------------- */
/**
* Creates a new, empty map with the default initial table size (16).
* 所有值都是默认值(默认长度16) table的初始化是延迟初始化
*/
public ConcurrentHashMap() {
}
/**
* Creates a new, empty map with an initial table size
* accommodating the specified number of elements without the need
* to dynamically resize.
*
* @param initialCapacity The implementation performs internal
* sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of
* elements is negative
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
//initialCapacity》=最大值的一半
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
//传入16 -》25 会变成32
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
/**
* sizeCtl > 0
* 当目前table未初始化时,sizeCtl表示初始化容量
*/
this.sizeCtl = cap;
}
/**
* Creates a new map with the same mappings as the given map.
*
* @param m the map
*/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}) and
* initial table density ({@code loadFactor}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @throws IllegalArgumentException if the initial capacity of
* elements is negative or the load factor is nonpositive
*
* @since 1.6
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}), table
* density ({@code loadFactor}), and number of concurrently
* updating threads ({@code concurrencyLevel}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation may use this value as
* a sizing hint.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* nonpositive
*/
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
//16/0.75 =23
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
//tableSizeFor(23) ==> 返回32
MAXIMUM_CAPACITY : tableSizeFor((int)size);
/**
* sizeCtl > 0
* 当目前table未初始化时,sizeCtl表示初始化容量
*/
this.sizeCtl = cap;
}
7_核心方法之写操作 put(k, v) 方法源码分解(非常重点)
7.1——putVal()方法源码分解
public V put(K key, V value) {
//onlyIfAbsent:false 有相应的key和value替换 true有相应的key和value就不能成功写入
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//控制k 和 v 不能为null
if (key == null || value == null) throw new NullPointerException();
//通过spread方法,可以让高位也能参与进寻址运算。
int hash = spread(key.hashCode());
//binCount表示当前k-v 封装成node后插入到指定桶位后,在桶位中的所属链表的下标位置
//0 表示当前桶位为null,node可以直接放着
//2 表示当前桶位已经可能是红黑树
int binCount = 0;
//tab 引用map对象的table
//自旋
for (Node<K,V>[] tab = table;;) {
//f 表示桶位的头结点
//n 表示散列表数组的长度
//i 表示key通过寻址计算后,得到的桶位下标
//fh 表示桶位头结点的hash值
Node<K,V> f; int n, i, fh;
//CASE1:成立,表示当前map中的table尚未初始化..
if (tab == null || (n = tab.length) == 0)
//最终当前线程都会获取到最新的map.table引用。
tab = initTable();
//CASE2:i 表示key使用路由寻址算法得到 key对应 table数组的下标位置,tabAt 获取指定桶位的头结点 f
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//进入到CASE2代码块 前置条件 当前table数组i桶位是Null时。
//使用CAS方式 设置 指定数组i桶位 为 new Node<K,V>(hash, key, value, null),并且期望值是null
//cas操作成功 表示ok,直接break for循环即可
//cas操作失败,表示在当前线程之前,有其它线程先你一步向指定i桶位设置值了。
//当前线程只能再次自旋,去走其它逻辑。
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//CASE3:前置条件,桶位的头结点一定不是null。
//条件成立表示当前桶位的头结点 为 FWD结点,表示目前map正处于扩容过程中..
else if ((fh = f.hash) == MOVED)
//看到fwd节点后,当前节点有义务帮助当前map对象完成迁移数据的工作
//学完扩容后再来看。
tab = helpTransfer(tab, f);
//CASE4:当前桶位 可能是 链表 也可能是 红黑树代理结点TreeBin
else {
//当插入key存在时,会将旧值赋值给oldVal,返回给put方法调用处..
V oldVal = null;
//使用sync 加锁“头节点”,理论上是“头结点”
synchronized (f) {
//为什么又要对比一下,看看当前桶位的头节点 是否为 之前获取的头结点?
//为了避免其它线程将该桶位的头结点修改掉,导致当前线程从sync 加锁 就有问题了。之后所有操作都不用在做了。
if (tabAt(tab, i) == f) {//条件成立,说明咱们 加锁 的对象没有问题,可以进来造了!
//条件成立,说明当前桶位就是普通链表桶位。
if (fh >= 0) {
//1.当前插入key与链表当中所有元素的key都不一致时,当前的插入操作是追加到链表的末尾,binCount表示链表长度
//2.当前插入key与链表当中的某个元素的key一致时,当前插入操作可能就是替换了。binCount表示冲突位置(binCount - 1)
binCount = 1;
//迭代循环当前桶位的链表,e是每次循环处理节点。
for (Node<K,V> e = f;; ++binCount) {
//当前循环节点 key
K ek;
//条件一:e.hash == hash 成立 表示循环的当前元素的hash值与插入节点的hash值一致,需要进一步判断
//条件二:((ek = e.key) == key ||(ek != null && key.equals(ek)))
// 成立:说明循环的当前节点与插入节点的key一致,发生冲突了
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//将当前循环的元素的 值 赋值给oldVal
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//当前元素 与 插入元素的key不一致 时,会走下面程序。
//1.更新循环处理节点为 当前节点的下一个节点
//2.判断下一个节点是否为null,如果是null,说明当前节点已经是队尾了,插入数据需要追加到队尾节点的后面。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//前置条件,该桶位一定不是链表
//条件成立,表示当前桶位是 红黑树代理结点TreeBin
else if (f instanceof TreeBin) {
//p 表示红黑树中如果与你插入节点的key 有冲突节点的话 ,则putTreeVal 方法 会返回冲突节点的引用。
Node<K,V> p;
//强制设置binCount为2,因为binCount <= 1 时有其它含义,所以这里设置为了2 回头讲 addCount。
binCount = 2;
//条件一:成立,说明当前插入节点的key与红黑树中的某个节点的key一致,冲突了
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
//将冲突节点的值 赋值给 oldVal
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//说明当前桶位不为null,可能是红黑树 也可能是链表
if (binCount != 0) {
//如果binCount>=8 表示处理的桶位一定是链表
if (binCount >= TREEIFY_THRESHOLD)
//调用转化链表为红黑树的方法
treeifyBin(tab, i);
//说明当前线程插入的数据key,与原有k-v发生冲突,需要将原数据v返回给调用者。
if (oldVal != null)
return oldVal;
break;
}
}
}
//1.统计当前table一共有多少数据
//2.判断是否达到扩容阈值标准,触发扩容。
//binCount:put》=1 桶位链表长度 =1,表示插入数据和桶位数据的key一致 也可能是也链表的每个Key一致 进行替换
// =0 插入的桶位 为null
// =2 桶位的数据为红黑树
//romove -1L binCount:-1 :删除 不用判断是不是扩容
addCount(1L, binCount);
return null;
}b
7.2——initTable()方法源码分解
map中的table尚未初始化 方法详解
/**
* Initializes table, using the size recorded in sizeCtl.
* * sizeCtl < 0
* * 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
* * 2.表示当前table数组正在进行扩容 ,高16位表示:扩容的标识戳 低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
* *
* * sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
* *
* * sizeCtl > 0
* *
* * 1. 如果table未初始化,表示初始化大小
* * 2. 如果table已经初始化,表示下次扩容时的 触发条件(阈值)
*/
private final Node<K,V>[] initTable() {
//tab 引用map.table
//sc sizeCtl的临时值
Node<K,V>[] tab; int sc;
//自旋 条件:map.table 尚未初始化
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
//大概率就是-1,表示其它线程正在进行创建table的过程,当前线程没有竞争到初始化table的锁。
Thread.yield(); // lost initialization race; just spin
//1.sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
//2.sizeCtl>0如果table未初始化,表示初始化大小
//3.sizeCtl>0如果table已经初始化,表示下次扩容时的 触发条件(阈值)
// -1 是一把锁 那个线程cas成功就可以进去
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//这里为什么又要判断呢? 防止其它线程已经初始化完毕了,然后当前线程再次初始化..导致丢失数据。
//条件成立,说明其它线程都没有进入过这个if块,当前线程就是具备初始化table权利了。
if ((tab = table) == null || tab.length == 0) {
//sc大于0 创建table时 使用 sc为指定大小,否则使用 16 默认值.
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//最终赋值给 map.table
table = tab = nt;
//n >>> 2 => 等于 1/4 n n - (1/4)n = 3/4 n => 0.75 * n
//sc 0.75 n 表示下一次扩容时的触发条件。
sc = n - (n >>> 2);
}
} finally {
//1.如果当前线程是第一次创建map.table的线程话,sc表示的是 下一次扩容的阈值
//2.表示当前线程 并不是第一次创建map.table的线程,当前线程进入到else if 块 时,将
//sizeCtl 设置为了-1 ,那么这时需要将其修改为 进入时的值。
sizeCtl = sc;
}
break;
}
}
return tab;
}
8_核心方法之统计addCount()方法源码分解(非常重点)
/**
* Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
*
* @param x the count to add
* @param check if <0, don't check resize, if <= 1 only check if uncontended
*/
private final void addCount(long x, int check) {
//as 表示 LongAdder.cells
//b 表示LongAdder.base
//s 表示当前map.table中元素的数量
CounterCell[] as; long b, s;
//条件一:true->表示cells已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据
// false->表示当前线程应该将数据累加到 base
//条件二:false->表示写base成功,数据累加到base中了,当前竞争不激烈,不需要创建cells
// true->表示写base失败,与其他线程在base上发生了竞争,当前线程应该去尝试创建cells。
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
//有几种情况进入到if块中?
//1.true->表示cells已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据
//2.true->表示写base失败,与其他线程在base上发生了竞争,当前线程应该去尝试创建cells。
//a 表示当前线程hash寻址命中的cell
CounterCell a;
//v 表示当前线程写cell时的期望值
long v;
//m 表示当前cells数组的长度
int m;
//true -> 未竞争 false->发生竞争
boolean uncontended = true;
//条件一:as == null || (m = as.length - 1) < 0
//true-> 表示当前线程是通过 写base竞争失败 然后进入的if块,就需要调用fullAddCount方法去扩容 或者 重试.. LongAdder.longAccumulate
//条件二:a = as[ThreadLocalRandom.getProbe() & m]) == null 前置条件:cells已经初始化了
//true->表示当前线程命中的cell表格是个空,需要当前线程进入fullAddCount方法去初始化 cell,放入当前位置.
//条件三:!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)
// false->取反得到false,表示当前线程使用cas方式更新当前命中的cell成功
// true->取反得到true,表示当前线程使用cas方式更新当前命中的cell失败,需要进入fullAddCount进行重试 或者 扩容 cells。
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);
//考虑到fullAddCount里面的事情比较累,就让当前线程 不参与到 扩容相关的逻辑了,直接返回到调用点。
return;
}
//binCount:put》=1 桶位链表长度 =1,表示插入数据和桶位数据的key一致 也可能是也链表的每个Key一致 进行替换
// =0 插入的桶位 为null
// =2 桶位的数据为红黑树
//romove -1L binCount:-1 :删除 不用判断是不是扩容
if (check <= 1)
return;
//获取当前散列表元素个数,这是一个期望值
s = sumCount();
}
//表示一定是一个put操作调用的addCount
if (check >= 0) {
//tab 表示map.table
//nt 表示map.nextTable
//n 表示map.table数组的长度
//sc 表示sizeCtl的临时值
Node<K,V>[] tab, nt; int n, sc;
/**
* sizeCtl < 0
* 1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
* 2.表示当前table数组正在进行扩容 ,高16位表示:扩容的标识戳 低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
*
* sizeCtl = 0,表示创建table数组时 使用DEFAULT_CAPACITY为大小
*
* sizeCtl > 0
*
* 1. 如果table未初始化,表示初始化大小
* 2. 如果table已经初始化,表示下次扩容时的 触发条件(阈值)
*/
//自旋
//条件一:s >= (long)(sc = sizeCtl)
// true-> 1.当前sizeCtl为一个负数 表示正在扩容中..
// 2.当前sizeCtl是一个正数,表示扩容阈值
// false-> 表示当前table尚未达到扩容条件
//条件二:(tab = table) != null
// 恒成立 true
//条件三:(n = tab.length) < MAXIMUM_CAPACITY
// true->当前table长度小于最大值限制,则可以进行扩容。
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//扩容批次唯一标识戳
//16 -> 32 扩容 标识为:1000 0000 0001 1011
int rs = resizeStamp(n);
//条件成立:表示当前table正在扩容
// 当前线程理论上应该协助table完成扩容
if (sc < 0) {
//条件一:(sc >>> RESIZE_STAMP_SHIFT) != rs
// true->说明当前线程获取到的扩容唯一标识戳 非 本批次扩容
// false->说明当前线程获取到的扩容唯一标识戳 是 本批次扩容
//条件二: JDK1.8 中有bug jira已经提出来了 其实想表达的是 = sc == (rs << 16 ) + 1
// true-> 表示扩容完毕,当前线程不需要再参与进来了
// false->扩容还在进行中,当前线程可以参与
//条件三:JDK1.8 中有bug jira已经提出来了 其实想表达的是 = sc == (rs<<16) + MAX_RESIZERS
// true-> 表示当前参与并发扩容的线程达到了最大值 65535 - 1
// false->表示当前线程可以参与进来
//条件四:(nt = nextTable) == null
// true->表示本次扩容结束
// false->扩容正在进行中
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//前置条件:当前table正在执行扩容中.. 当前线程有机会参与进扩容。
//条件成立:说明当前线程成功参与到扩容任务中,并且将sc低16位值加1,表示多了一个线程参与工作
//条件失败:1.当前有很多线程都在此处尝试修改sizeCtl,有其它一个线程修改成功了,导致你的sc期望值与内存中的值不一致 修改失败
// 2.transfer 任务内部的线程也修改了sizeCtl。
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
//协助扩容线程,持有nextTable参数
transfer(tab, nt);
}
//1000 0000 0001 1011 0000 0000 0000 0000 +2 => 1000 0000 0001 1011 0000 0000 0000 0010
//条件成立,说明当前线程是触发扩容的第一个线程,在transfer方法需要做一些扩容准备工作
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
//触发扩容条件的线程 不持有nextTable
transfer(tab, null);
s = sumCount();
}
}
}
9_超级核心方法之扩容transfer()方法源码分解(重点中的重点!!)
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//n 表示扩容之前table数组的长度
//stride 表示分配给线程任务的步长
int n = tab.length, stride;
//方便讲解源码 stride 固定为 16
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//条件成立:表示当前线程为触发本次扩容的线程,需要做一些扩容准备工作
//条件不成立:表示当前线程是协助扩容的线程..
if (nextTab == null) { // initiating
try {
//创建了一个比扩容之前大一倍的table
@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 ,方便协助扩容线程 拿到新表
nextTable = nextTab;
//记录迁移数据整体位置的一个标记。index计数是从1开始计算的。
transferIndex = n;
}
//表示新数组的长度
int nextn = nextTab.length;
//fwd 节点,当某个桶位数据处理完毕后,将此桶位设置为fwd节点,其它写线程 或读线程看到后,会有不同逻辑。
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//推进标记
boolean advance = true;
//完成标记
boolean finishing = false; // to ensure sweep before committing nextTab
//i 表示分配给当前线程任务,执行到的桶位
//bound 表示分配给当前线程任务的下界限制
int i = 0, bound = 0;
//自旋
for (;;) {
//f 桶位的头结点
//fh 头结点的hash
Node<K,V> f; int fh;
/**
* 1.给当前线程分配任务区间
* 2.维护当前线程任务进度(i 表示当前处理的桶位)
* 3.维护map对象全局范围内的进度
*/
while (advance) {
//分配任务的开始下标
//分配任务的结束下标
int nextIndex, nextBound;
//CASE1:
//条件一:--i >= bound
//成立:表示当前线程的任务尚未完成,还有相应的区间的桶位要处理,--i 就让当前线程处理下一个 桶位.
//不成立:表示当前线程任务已完成 或 者未分配
if (--i >= bound || finishing)
advance = false;
//CASE2:
//前置条件:当前线程任务已完成 或 者未分配
//条件成立:表示对象全局范围内的桶位都分配完毕了,没有区间可分配了,设置当前线程的i变量为-1 跳出循环后,执行退出迁移任务相关的程序
//条件不成立:表示对象全局范围内的桶位尚未分配完毕,还有区间可分配
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
//CASE3:
//前置条件:1、当前线程需要分配任务区间 2.全局范围内还有桶位尚未迁移
//条件成立:说明给当前线程分配任务成功
//条件失败:说明分配给当前线程失败,应该是和其它线程发生了竞争吧
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
//CASE1:
//条件一:i < 0
//成立:表示当前线程未分配到任务
if (i < 0 || i >= n || i + n >= nextn) {
//保存sizeCtl 的变量
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
//条件成立:说明设置sizeCtl 低16位 -1 成功,当前线程可以正常退出
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//1000 0000 0001 1011 0000 0000 0000 0000
//条件成立:说明当前线程不是最后一个退出transfer任务的线程
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
//正常退出
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
//前置条件:【CASE2~CASE4】 当前线程任务尚未处理完,正在进行中
//CASE2:
//条件成立:说明当前桶位未存放数据,只需要将此处设置为fwd节点即可。
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
//CASE3:
//条件成立:说明当前桶位已经迁移过了,当前线程不用再处理了,直接再次更新当前线程任务索引,再次处理下一个桶位 或者 其它操作
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
//CASE4:
//前置条件:当前桶位有数据,而且node节点 不是 fwd节点,说明这些数据需要迁移。
else {
//sync 加锁当前桶位的头结点
synchronized (f) {
//防止在你加锁头对象之前,当前桶位的头对象被其它写线程修改过,导致你目前加锁对象错误...
if (tabAt(tab, i) == f) {
//ln 表示低位链表引用
//hn 表示高位链表引用
Node<K,V> ln, hn;
//条件成立:表示当前桶位是链表桶位
if (fh >= 0) {
//lastRun
//可以获取出 当前链表 末尾连续高位不变的 node
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;
}
}
//条件成立:说明lastRun引用的链表为 低位链表,那么就让 ln 指向 低位链表
if (runBit == 0) {
ln = lastRun;
hn = null;
}
//否则,说明lastRun引用的链表为 高位链表,就让 hn 指向 高位链表
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;
}
//条件成立:表示当前桶位是 红黑树 代理结点TreeBin
else if (f instanceof TreeBin) {
//转换头结点为 treeBin引用 t
TreeBin<K,V> t = (TreeBin<K,V>)f;
//低位双向链表 lo 指向低位链表的头 loTail 指向低位链表的尾巴
TreeNode<K,V> lo = null, loTail = null;
//高位双向链表 lo 指向高位链表的头 loTail 指向高位链表的尾巴
TreeNode<K,V> hi = null, hiTail = null;
//lc 表示低位链表元素数量
//hc 表示高位链表元素数量
int lc = 0, hc = 0;
//迭代TreeBin中的双向链表,从头结点 至 尾节点
for (Node<K,V> e = t.first; e != null; e = e.next) {
// h 表示循环处理当前元素的 hash
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;
//将低位链表尾指针指向 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;
}
}
}
}
}
}
10_核心方法之helpTransfer()协助扩容方法源码分解(重点中的重点!!)
/**
* Helps transfer if a resize is in progress.
*/
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
//nextTab 引用的是 fwd.nextTable == map.nextTable 理论上是这样。
//sc 保存map.sizeCtl
Node<K,V>[] nextTab; int sc;
//条件一:tab != null 恒成立 true
//条件二:(f instanceof ForwardingNode) 恒成立 true
//条件三:((ForwardingNode<K,V>)f).nextTable) != null 恒成立 true
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
//拿当前标的长度 获取 扩容标识戳 假设 16 -> 32 扩容:1000 0000 0001 1011
int rs = resizeStamp(tab.length);
//条件一:nextTab == nextTable
//成立:表示当前扩容正在进行中
//不成立:1.nextTable被设置为Null 了,扩容完毕后,会被设为Null
// 2.再次出发扩容了...咱们拿到的nextTab 也已经过期了...
//条件二:table == tab
//成立:说明 扩容正在进行中,还未完成
//不成立:说明扩容已经结束了,扩容结束之后,最后退出的线程 会设置 nextTable 为 table
//条件三:(sc = sizeCtl) < 0
//成立:说明扩容正在进行中
//不成立:说明sizeCtl当前是一个大于0的数,此时代表下次扩容的阈值,当前扩容已经结束。
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
//条件一:(sc >>> RESIZE_STAMP_SHIFT) != rs
// true->说明当前线程获取到的扩容唯一标识戳 非 本批次扩容
// false->说明当前线程获取到的扩容唯一标识戳 是 本批次扩容
//条件二: JDK1.8 中有bug jira已经提出来了 其实想表达的是 = sc == (rs << 16 ) + 1
// true-> 表示扩容完毕,当前线程不需要再参与进来了
// false->扩容还在进行中,当前线程可以参与
//条件三:JDK1.8 中有bug jira已经提出来了 其实想表达的是 = sc == (rs<<16) + MAX_RESIZERS
-+-+ //条件四:transferIndex <= 0
// true->说明map对象全局范围内的任务已经分配完了,当前线程进去也没活干..
// false->还有任务可以分配。
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}