类注释
- 取数方法没有加锁,所以会被存数方法影响
- 聚合方法:size/isEmpty/containsValue,在没有被并发更新的情况下是准确的,但是存在并发更新时,上述聚合方法只是反映了map的一个瞬时状态,这种瞬时状态只能用于监测或估算,而不能用于程序控制
- 和Hashtable一样,和HashMap相反,不允许使用null作为key或value
属性
常量
MAXIMUM_CAPACITY
- map在确定数组下标时,采用的是
(length-1)&hash的方式,只有当length为2的指数幂的时候才能较均匀的分布元素。所以map规定了其容量必须是2的n次方,使用位运算同时还提高了Java的处理速度 - map内部由
Entry[]数组构成,Java的数组下标是由int表示的。所以对于map来说其最大的容量应该是不超过int最大值的一个2的指数幂,而最接近int最大值的2的指数幂用位运算符表示就是1<<30
LOAD_FACTOR
- 在构造函数中指定loadFactor只能影响初次构造的map的capacity,后续不会用到
- 其实LOAD_FACTOR的值不会常用,因为直接用
n-(n>>>2)即表示n的3/4,即n*LOAD_FACTOR
TREEIFY_THRESHOLD/UNTREEIFY_THRESHOLD
- TREEIFY_THRESHOLD设置为8的原因:TreeNode占用空间是Node的两倍,而且结点数较少时,红黑树的查找效率跟链表相差不大,所以只在结点数量较多时用红黑树才能得到时间和空间上的tradeoff。在随机hash和LOAD_FACTOR的前提下,bins中的结点分布符合泊松分布,在一个bin中节点数达到8的概率是0.00000006,所以使用8作为阈值是很考究的。
- UNTREEIFY_THRESHOLD设置为6的原因:使用6也是一个tradeoff,在TREEIFY_THRESHOLD使用8的前提下,如果UNTREEIFY_THRESHOLD使用7,则可能存在对同一个bin频繁插入和删除的操作就会导致bin频繁的转为红黑树和链表,如果UNTREEIFY_THRESHOLD使用6以下的值,那红黑树在空间和时间效率上并不比链表优良,所以使用6是最合适的
MIN_TRANSFER_STRIDE
- 扩容操作中,transfer这个步骤是允许多线程的,这个常量表示一个线程执行transfer时,最少要对连续的16个hash桶进行transfer(不足16就按16算,多控制下正负号就行)。也就是单线程执行transfer时的最小任务量,单位为一个hash桶,这就是线程的transfer的步进(stride)
- 最小值是DEFAULT_CAPACITY,不使用太小的值,避免太小的值引起transfer时线程竞争过多,如果计算出来的值小于此值,就使用此值。正常步骤中会根据CPU核心数目来算出实际的,一个核心允许8个线程并发执行扩容操作的transfer步骤,这个8是个经验值,不能调整的
- 因为transfer操作不是IO操作,也不是死循环那种100%的CPU计算,CPU计算率中等,1核心允许8个线程并发完成扩容,理想情况下也算是比较合理的值。一段代码的IO操作越多,1核心对应的线程就要相应设置多点,CPU计算越多,1核心对应的线程就要相应设置少一些
- 表明:默认的容量是16,也就是默认构造的实例,第一次扩容实际上是单线程执行的,看上去是可以多线程并发(方法允许多个线程进入),但是实际上其余的线程都会被一些if判断拦截掉,不会真正去执行扩容
MOVED/TREEBIN/RESERVED/HASH_BITS
- MOVED:ForwardingNode的hash值,ForwardingNode是一种临时节点,在扩进行中才会出现,并且它不存储实际的数据。如果旧数组的一个hash桶中全部的节点都迁移到新数组中,旧数组就在这个hash桶中放置一个ForwardingNode。读操作或者迭代读时碰到ForwardingNode时,将操作转发到扩容后的新的table数组上去执行,写操作碰见它时,则尝试帮助扩容
- TREEBIN:TreeBin的hash值,TreeBin是ConcurrentHashMap中用于代理操作TreeNode的特殊节点,持有存储实际数据的红黑树的根节点。因为红黑树进行写入操作,整个树的结构可能会有很大的变化,这个对读线程有很大的影响,所以TreeBin还要维护一个简单读写锁,这是相对HashMap,这个类新引入这种特殊节点的重要原因
- RESERVED:ReservationNode的hash值,ReservationNode是一个保留节点,就是个占位符,不会保存实际的数据,正常情况是不会出现的,在jdk1.8新的函数式有关的两个方法computeIfAbsent和compute中才会出现
- HASH_BITS:用于和负数hash值进行
&运算,将符号位置为0,将其转化为正数(绝对值不相等),Hashtable中定位hash桶也有使用这种方式来进行负数转正数
NCPU
- CPU的核心数,用于在扩容时计算一个线程一次要干多少活
serialPersistentFields
- 在序列化时使用,这是为了兼容以前的版本
变量
Node<K,V>[] table
- Node数组,用volatile修饰,通过Unsafe方法读写
Node<K,V>[] nextTable
- 扩容后的新的table数组,只有在扩容时才有用
nextTable != null,说明扩容方法还没有真正退出,一般可以认为是此时还有线程正在进行扩容,极端情况需要考虑此时扩容操作只差最后给几个变量赋值(包括nextTable = null)的这个大的步骤,这个大步骤执行时,通过sizeCtl经过一些计算得出来的扩容线程的数量是0
long baseCount
- 计数器基本值,主要在没有碰到多线程竞争时使用,需要通过CAS进行更新
int sizeCtl
- 非常重要的一个属性,源码中的英文翻译,直译过来是下面的四行文字的意思
sizeCtl = -1,表示有线程正在进行真正的初始化操作sizeCtl = -(1 + nThreads),表示有nThreads个线程正在进行扩容操作sizeCtl > 0,表示接下来的真正的初始化操作中使用的容量,或者初始化/扩容完成后的thresholdsizeCtl = 0,默认值,此时在真正的初始化操作中使用默认容量
- 但是,通过我对源码的理解,这段注释实际上是有问题的,有问题的是第二句,
sizeCtl = -(1 + nThreads)这个,网上好多都是用第二句的直接翻译去解释代码,这样理解是错误的。默认构造的16个大小的ConcurrentHashMap,只有一个线程执行扩容时,sizeCtl = -2145714174,但是照这段英文注释的意思,sizeCtl的值应该是-(1 + 1) = -2,sizeCtl在小于0时的确有记录有多少个线程正在执行扩容任务的功能,但是不是这段英文注释说的那样直接用-(1 + nThreads),实际中使用了一种生成戳,根据生成戳算出一个基数,不同轮次的扩容操作的生成戳都是唯一的,来保证多次扩容之间不会交叉重叠,当有n个线程正在执行扩容时,sizeCtl在值变为(基数 + n),1.8.0_111的源码的383-384行写了个说明:A generation stamp in field sizeCtl ensures that resizings do not overlap.
int transferIndex
- 下一个transfer任务的起始下标index加上1之后的值,transfer时下标index从
length - 1开始往0走。transfer时方向是倒过来的,迭代时是下标从小往大,二者方向相反,尽量减少扩容时transefer和迭代两者同时处理一个hash桶的情况,顺序相反时,二者相遇过后,迭代没处理的都是已经transfer的hash桶,transfer没处理的,都是已经迭代的hash桶,冲突会变少。 - 下标在[nextIndex - 实际的stride (下界要 >= 0), nextIndex - 1]内的hash桶,就是每个transfer的任务区间,每次接受一个transfer任务,都要CAS执行
transferIndex = transferIndex - 实际的stride,保证一个transfer任务不会被几个线程同时获取(相当于任务队列的size减1),当没有线程正在执行transfer任务时,一定有transferIndex <= 0,这是判断是否需要帮助扩容的重要条件(相当于任务队列为空)
int cellsBusy
- CAS自旋锁标志位,用于初始化,或者counterCells扩容时
CounterCell[] counterCells
- 用于高并发的计数单元,如果初始化了这些计数单元,那么跟table数组一样,长度必须是2^n的形式
KeySetView<K,V> keySet/ValuesView<K,V> values/EntrySetView<K,V> entrySet
- 视图变量
方法
静态方法
int spread(int h)
- 将高16位与低16位异或,并将符号位置为0
- 这样做是因为在计算node的index时,是用2的幂作为掩码,所以只用低位进行计算,存在大量的碰撞,比如一些Float的值,所以将高位的影响扩散到低位,可以减少这种碰撞。同时,因为table的容量限制,hash中的高位在计算index时很难被用到
- 处于对速度、效能和bit位扩散的质量的考虑,并且使用红黑树处理大量的碰撞,所以只是简单的将高位和低位进行异或就够了
int tableSizeFor(int c)
- 返回大于输入参数且最近的2的整数次幂的数
先分析有关n位操作部分 假设n的二进制为01xxx...xxx。接着 对n右移1位:001xx...xxx,再位或:011xx...xxx 对n右移2为:00011...xxx,再位或:01111...xxx 此时前面已经有四个1了,再右移4位且位或可得8个1 同理,有8个1,右移8位肯定会让后八位也为1 综上可得,该算法让最高位的1后面的位全变为1 最后再让结果n+1,即得到了2的整数次幂的值了 现在回来看看第一条语句:int n = cap - 1 让cap-1再赋值给n的目的是令找到的目标值大于或等于原值。例如二进制1000,十进制数值为8 如果不对它减1而直接操作,将得到答案10000,即16。显然不是结果。减1后二进制为111,再进行操作则会得到原来的数值1000,即8
table元素访问方法
<K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i)- volatile语义获取table元素i
<K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v)- CAS将位置i的元素c替换为v
<K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)- volatile语义将位置i的元素设置为v
- 用Unsafe类实现这三个方法的原因是,Java的数组在元素层面上的设计缺失,无法表达元素是final和volatile的语义,所以使用getObjectVolatile补充volatile的语义,使用@Stable补充final的语义。数组元素本身和没有volatile修饰的字段一样,无法保证线程之间的可见性,只有触发happens-before关系的操作,才能保证线程之间的可见性。比如使用
table[0] = new Object()直接赋值,这个赋值不会触发任何happens-before关系的操作,相当于对一个无volatile变量进行赋值一样
构造函数
ConcurrentHashMap()ConcurrentHashMap(int initialCapacity)ConcurrentHashMap(Map<? extends K, ? extends V> m)ConcurrentHashMap(int initialCapacity, float loadFactor)ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)long size = (long)(1.0 + (long)initialCapacity / loadFactor);,initialCapacity * loadFactor = resizeThreshold,所以这里的initialCapacity / loadFactor表示乘以loadFactory后得到的resizeThreshold就是initialCapacity,即size达到initialCapacity后就会进行扩容,因此如果放到map中的元素数量刚好是initialCapacity个,那就避免了扩容操作,而+1.0相当于是使用Math.ceil将浮点数向上取整,不过如果initialCapacity/loadFactor是正数,就会多出一个元素,再用tableSizeFor调整size,就得到了最合适的capacity。比如initalCapacity=16,loadFactor=0.75,则size=22
get
源码
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 重hash
int h = spread(key.hashCode());
// 先看bin是否有结点
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// bin的头结点就是要找的结点
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// hash值小于0,都是特殊节点,调用find方法查询
// 包括已经迁移的结点、树节点、临时节点
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 链表查找
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
put
源码
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;
// table为空则初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 如果bin没有结点,则直接CAS放入,失败则自旋重试
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
}
// 如果是fwd结点,表示正在迁移,则当前线程参与迁移table
// 迁移结束后,自旋重试
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 说明是链表或者红黑树
else {
V oldVal = null;
// 对bin加锁
synchronized (f) {
// 检查是否仍然是之前的头结点
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;
// onlyIfAbsent表示只有key不存在时才赋值,否则仍然为旧值
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
// 链表遍历完仍然没有key,则放入新结点到链表尾
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);
// 如果之前已经存在该key,则直接返回
if (oldVal != null)
return oldVal;
break;
}
}
}
// 如果key之前不存在,则是新增结点,需要自增count
addCount(1L, binCount);
return null;
}
initTable()
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// sizeCtl小于0表示已经有线程在初始化table了
// 直接将cpu时间让出去,等他们初始化完,再自旋检查下
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// 将sizeCtl设置为-1,表示当前线程将要初始化table
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 再次检查table是否仍然为空
if ((tab = table) == null || tab.length == 0) {
// 如果sizeCtl>0,表示创建Map时指定了初始化容量
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 将sizeCtl置为当前容量的0.75,表示触发扩容的阈值
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
treeifyBin(Node<K,V>[],int)
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
// 如果table容量小于能支持红黑树的阈值,则只扩容
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
// 判断索引位置的bin是链表结点
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
// 对bin加锁
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;
}
// 创建TreeBin即红黑树,并放置在索引处
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
tryPresize()
private final void tryPresize(int size) {
// 给tableSizeFor传1.5倍size,可以理解为将0.75size是触发扩容的阈值
// 而1.5size是下次触发扩容的阈值,所以tableSizeFor会返回该阈值对应的容量
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;
if (tab == null || (n = tab.length) == 0) {
// sc>0表示创建Map时指定了capacity,这里用sc和c中最大值
n = (sc > c) ? sc : c;
// sizeCtl置为-1表示正在初始化
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;
}
}
}
// 在table已经初始化的情况下,sc表示下次扩容的阈值
// 这里c<sc,表示容量已经够了,不需要扩容,可能是其他线程已经扩容了
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
// 校验是否还是之前的table,是否有其他线程已经初始化了
else if (tab == table) {
int rs = resizeStamp(n);
// sc<0,表示正在初始化或者正在扩容
if (sc < 0) {
Node<K,V>[] nt;
// sc>>>RESIZE_STAMP_SHIFT!=rs,判断高位的扩容标记不相同,则不能参与扩容
// sc == rs + 1,搜了下是个BUG,正确应该是sc==rs<<RESIZE_STAMP_SHIFT+1,这样才表示所有线程已经扩容完毕
// sc == rs + MAX_RESIZERS ,同上,应该是sc==rs<<RESIZE_STAMP_SHIFT+MAX_RESIZERS,表示参与扩容的线程数量已经达到最大值
// (nt = nextTable) == null,表示扩容已经结束
// transferIndex <= 0,表示所有的transfer任务都被领取完了,不再需要帮助扩容
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);
}
// 开始扩容,第一个线程会+2,后续线程会+1
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
}
}
}
transfer(Node<K,V>[],Node<K,V>)
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
// stride表示每个线程每个批次负责的bin数量,由transferIndex指定起始右边界
// 从后往前,[transferIndex-stride, transferIndex]区间的bin都归某个线程在该批次迁移
// 所以总共有ceil(length/stride)个批次的任务
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
// 由第一个触发transfer的线程初始化nextTable
// 扩容后的容量为之前的两倍
// transferIndex初始化为length,所以迁移是从后往前的
// transferIndex表示迁移区间的分割索引,左边是未迁移或者正在迁移的元素,右边是已经迁移的元素
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;
}
// fwd是标志节点,表示该bin已经迁移到nextTable
// 如果要查找oldTable中该bin的元素,可以通过fwd访问nextTable
int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// advance表示是否还需要继续迁移
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
// i表示正在迁移的bin的索引
// bound表示当前迁移区间的左边界,transferIndex是右边界
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
// advance为true表示需要计算bin索引和区间边界
while (advance) {
int nextIndex, nextBound;
// 如果索引i已经超出左边界,或者迁移已经结束,就不再需要迁移
if (--i >= bound || finishing)
advance = false;
// 如果迁移右边界<=0,表示已经迁移完成
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// 右边界向左移动一个stride的距离
// 如果已经不足一个stride的元素,则置为0
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
// bound为迁移区间左边界
// i为下一个将要迁移的bin的索引
// advence赋值为false,退出while循环,结束计算
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// 校验是否已经迁移完成
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 迁移完成,赋值table,并清除nextTable
// 设置sizeCtl为下次触发扩容的容量,旧容量的1.5,所以是新容量的0.75
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// sizeCtl在迁移前会设置为(rs<<RESIZE_STAMP_SHIFT)+2
// 之后每有一个线程参与迁移就将sizeCtl加1
// 这里sizeCtl减1,表示当前线程已经完成了迁移任务
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 表示所有线程都已经完成迁移任务,退出
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 这里表示还有其他线程没有将sizeCtl减1
// 将finishing置为ture,会走到上面的if(finishing)
finishing = advance = true;
i = n; // recheck before commit
}
}
// 如果这里没有结点,则放入fwd
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
// 如果这里已经是fwd,表示已经迁移过了,则迁移下一个bin
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
// 对当前bin加锁,开始处理bin处所有结点的迁移工作
synchronized (f) {
// 校验看是否仍然是之前的bin
if (tabAt(tab, i) == f) {
// ln为low node,hn为high node
// ln表示迁移后在原位置的结点,hn表示迁移后在n+i位置的结点
Node<K,V> ln, hn;
// fh>=0表示链表结点
if (fh >= 0) {
// runBit表示当前结点是ln还是hn
// n是2的幂,所以fh&n要么是0要么是n
// 0表示仍然在原位置,n表示在n+i位置
// 用于找到最后一组和头结点在同一个位置的结点
int runBit = fh & n;
// lastRun表示最后一组和头结点在同一个位置的第一个结点
// runBit和lastRun的作用是找出最后一组和头结点在同一个位置的结点
// 这样头结点和最后一组结点就可以不用迁移,直接更改next指针即可
Node<K,V> lastRun = f;
// 找到链表中的lastRun,lastRun及其之后的结点是一起迁移的
// lastRun之前的结点需要放到新结点中,然后分到ln和hn两个链表中
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
// runBit=0表示lastRun在原位置
// runBit=n表示lastRun在n+i位置
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
// 将原链表拆分成ln和hn两个链表
// 除了最后一组一起迁移的节点,前面的节点在新链表中都是倒序
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;
// lo表示迁移后在原位置的双向链表头结点,loTail为尾结点
// hi表示迁移后在n+i位置的双向链表头结点,hiTail为尾结点
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
// lc和hc分别表示lo和hi双向链表的结点数量
int lc = 0, hc = 0;
// 将结点拆分成lo和hi两个双向链表,后面再处理树结构
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;
}
}
}
}
}
}
addCount(int,int)
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 如果counterCells不为空
// 或者baseCount+x失败
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
// 如果counterCells为空,或者counterCells没有元素
// 或者当前线程没有计数器单元
// 或者当前线程计数器单元的值加上x失败
// 则调用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;
}
// check表示是否需要检查bin总数,<=1则不需要
if (check <= 1)
return;
// 统计当前bin总数
s = sumCount();
}
// 走到这里说明check>1,或者baseCount+x成功,则检查是否需要扩容
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);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
本文深入分析了ConcurrentHashMap的源码,包括其类注释、属性和方法。重点讨论了并发控制、容量限制、负载因子、树化阈值以及扩容策略。在了解这些细节后,读者能够更好地理解和利用ConcurrentHashMap在多线程环境下的高效特性。

617

被折叠的 条评论
为什么被折叠?



