重要常量
/* ---------------- Constants -------------- */
/**
* table最大容量是2的30次方
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* table默认初始化容量16 扩容总是2的n次方。
*/
private static final int DEFAULT_CAPACITY = 16;
/**
* The largest possible (non-power of two) array size.
* Needed by toArray and related methods.
*/
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* The default concurrency level for this table. Unused but
* defined for compatibility with previous versions of this class.
*/
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* 负载因子
*/
private static final float LOAD_FACTOR = 0.75f;
/**
* 树化阈值,当桶内链表长度>=8时,会将链表转成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 当桶内node小于6时,红黑树会转成链表。
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* table的总容量要大于64,桶内链表才转换为树形结构,否则当桶内链表长度>=8 时会优先扩容。
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* Minimum number of rebinnings per transfer step. Ranges are
* subdivided to allow multiple resizer threads. This value
* serves as a lower bound to avoid resizers encountering
* excessive memory contention. The value should be at least
* DEFAULT_CAPACITY.
*/
private static final int MIN_TRANSFER_STRIDE = 16;
/**
* The number of bits used for generation stamp in sizeCtl.
* Must be at least 6 for 32bit arrays.
*/
private static int RESIZE_STAMP_BITS = 16;
/**
* The maximum number of threads that can help resize.
* Must fit in 32 - RESIZE_STAMP_BITS bits.
*/
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
/**
* The bit shift for recording size stamp in sizeCtl.
*/
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
/*
* Encodings for Node hash fields. See above for explanation.
*/
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
/** Number of CPUS, to place bounds on some sizings */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* 整个hash表的结构。也被称作hash桶数组
*/
transient volatile Node<K,V>[] table;
/**
* The next table to use; non-null only while resizing.
*/
private transient volatile Node<K,V>[] nextTable;
/**
* Base counter value, used mainly when there is no contention,
* but also as a fallback during table initialization
* races. Updated via CAS.
*/
private transient volatile long baseCount;
/**
* 该字段控制table的初始化和扩容。
* sizeCtl<0 时,表示table初始化或者扩容。
* sizeCtl = -1 表示已经初始化。
* sizeCtl = -(1+正在扩容的线程数)
* 大于零,表示size * 0.75。
*/
private transient volatile int sizeCtl;
/**
* The next table index (plus one) to split while resizing.
*/
private transient volatile int transferIndex;
/**
* Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
*/
private transient volatile int cellsBusy;
/**
* Table of counter cells. When non-null, size is a power of 2.
*/
private transient volatile CounterCell[] counterCells;
// views
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;
内部类
Node
static class Node<K,V> implements Map.Entry<K,V> {
/**
* hash值
* -1为ForwardingNode表示正在扩容
* -2为TreeBin表示桶内为红黑树
* 大于0,表示桶内为链表
*/
final int hash;
final K key;
volatile V val;
// 下一个Node
volatile Node<K,V> next;
...
}
TreeNode 继承自Node,含有Node的所有成员
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;
...
}
TreeBin 树的包装类Node
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 锁状态标识
// 1 写锁
static final int WRITER = 1; // set while holding write lock
// 2 等待写锁
static final int WAITER = 2; // set when waiting for write lock
// 4 读锁
static final int READER = 4; // increment value for setting read lock
}
ForwardingNode
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
}
在扩容时使用,如果旧table的一个桶中全部的节点都迁移到新table中,旧table就在这个桶中放置一个ForwardingNode。
读操作或者迭代读时碰到ForwardingNode时,将操作转发到扩容后的新的table上去执行。
写操作碰见它时,则尝试帮助扩容。
Unsafe类方法
@SuppressWarnings("unchecked")
// transient volatile Node<K,V>[] table; table成员变量定义用volatile修饰
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
// 如果tab是volatile变量,则该方法保证其可见性
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// 通过CAS设置table索引为 i 处的元素
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 修改table 索引 i 处的元素
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
// 如果tab是volatile变量,则该方法保证其可见性
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
构造函数
- 无参构造函数
/**
* 啥也不做,使用默认容量16
*/
public ConcurrentHashMap() {
}
- 传入初始容量的构造函数
并不是把入参当作容量,table的容量必须是2的N次方
public ConcurrentHashMap(int initialCapacity) {
// 入参<0,抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException();
// 给定容量大于最大容量,则使用最大容量 2的30次方,否则需要计算
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
// tableSizeFor和HashMap对比传参是不一样的
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
对比一下HashMap和ConcurrentHashMap的tableSizeFor()方法传参
// tableSizeFor(initialCapacity);
// HashMap初始化容量时,转化为大于等于设置值的2的幂次数
Map map = new HashMap(2) 实际初始化的大小为2
Map map = new HashMap(14) 实际初始化的大小为16
Map map = new HashMap(16) 实际初始化的大小为16
// tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
// ConcurrentHashMap初始化设置容量时,底层会自动转换为距设置值2倍的最近的2的次幂;
// 前后距离相等时选择向后转换:如3 * 2 = 6距离4和8相等,选择向后转换为8
Map map = new ConcurrentHashMap(2) 实际初始化的大小为4
Map map = new ConcurrentHashMap(14) 实际初始化的大小为32
Map map = new ConcurrentHashMap(16) 实际初始化的大小为32
- 带负载因子的构造函数
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
- 带concurrencyLevel的构造函数
concurrencyLevel只是为了兼容1.7,并不是实际的并发等级
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;
}
table的初始化
和HashMap一样都是在首次put过程中判断出table数组为空或者length为0时才调用进行初始化的
/**
* Initializes table, using the size recorded in sizeCtl.
* 使用sizeCtl中记录的大小初始化table。
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
/**
* -1 其他线程正在初始化 当前线程yield
* -(1+n) 其他n个线程正在扩容 当前线程yield
* 0 使用了无参构造器,sizeCtl为默认值0 其他线程yield
* 大于0 已经在构造器中为sizeCtl重新赋值 其他线程yield
*/
if ((sc = sizeCtl) < 0)
// 让出CPU资源
Thread.yield(); // lost initialization race; just spin
//修改 sizeCtl 的值为 -1 SIZECTL 为 sizeCtl 的内存地址(通过Unsafe直接操作内存)
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
// 用户有指定初始化容量,就用用户指定的,否则用默认的16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 计算扩容阈值,比如: 16-4 = 12
sc = n - (n >>> 2);
}
} finally {
// 设置阈值
// ps: 本来sizeCtl就是存放扩容阈值的,table初始化前没用到,所以暂时用来存用户指定容量时计算出来的容量,HashMap的threShold也是这样用的
sizeCtl = sc;
}
break;
}
}
return tab;
}
put流程
public V putIfAbsent(K key, V value) {
// true表示key已经存在时,put失败
return putVal(key, value, true);
}
public V put(K key, V value) {
// false 表示key已经存在时,替换value
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key和value都不能为null,而HashMap是可以的
if (key == null || value == null) throw new NullPointerException();
// 计算Hash值,就一行代码return (h ^ (h >>> 16)) & HASH_BITS; // static final int HASH_BITS = 0x7fffffff // 0x7fffffff => 0b 01111111111111111111111111111111
// 保证hash码最高位为0,即正数
// Node节点的hash值(-1为ForwardingNode表示正在扩容,-2为TreeBin表示桶内为红黑树,大于0表示桶内为链表。)
int hash = spread(key.hashCode());
// 用来计算在这个桶总共有多少个元素,用来控制扩容或者转移为树
int binCount = 0;
for (Node<K,V>[] tab = table;;) { // 迭代桶数组,自旋
// f: 桶头节点 n: table的长度 i: 在table的下标 fh: 头节点的hash值
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0) // 尚未初始化
tab = initTable();
// 计算出桶下标(n - 1) & hash,通过UnSafe类提供的方法直接操作内存获取对应节点的数据
// [ConcurrentHashMap中tabAt方法分析](https://blog.youkuaiyun.com/ty649128265/article/details/91857242)
// 情况1 : 对应桶没有数据, 不加锁, 直接通过比较并交换插入
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
}
// 情况2:hash值为-1,说明是ForwardingNode节点 这个桶正在被执行扩容
else if ((fh = f.hash) == MOVED)
// 进入帮助扩容流程 后面分析
tab = helpTransfer(tab, f);
// 情况3:执行插入
else {
V oldVal = null;
// 锁住头结点f
synchronized (f) {
// 再次检查内存中f这个头结点有没有被修改过
if (tabAt(tab, i) == f) {
// 头结点hash码大于零,表示这个桶存的是链表
if (fh >= 0) {
binCount = 1;
// 遍历链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
// key 相同 时根据onlyIfAbsent判断是否更新旧值
// 通过putIfAbsent这个方法插入数据的场景onlyIfAbsent是true,不允许修改旧值
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;
// 遍历到链表尾部都没有遇到key相同的,插入到末尾
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 桶的头节点是个TreeBin
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;
}
}
}
}
// ==== 插入操作完成==
// binCount 的双重检查,提高效率
if (binCount != 0) {
// 链表长度超过8触发树化操作
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
// 如果是修改,则返回被修改的原值,否则oldVal就不会被赋值
if (oldVal != null)
return oldVal;
break;
}
}
}
// 计数器加1,完成新增后,table扩容,就是这里面触发
addCount(1L, binCount);
return null;
}
addCount方法 计数和触发扩容
先看看HashMap中元素个数的统计
// 由于HashMap使用在单线程的情况下,put插入之后直接元素个数size加1即可
if (++size > threshold) resize();
ConcurrentHashMap为势必需要通过加锁或者自旋来保证并发情况下共享变量的的安全,如果竞争比较激烈的情况下,size的设置上会出现比较大的冲突反而影响了性能,所以在ConcurrentHashMap采用了分片的方法来记录大小,下面通过统计元素个数的sumSize方法来简单看看ConCurrentHashMap是怎么做的
// 标识当前cell数组是否在初始化或扩容中的CAS标志位
private transient volatile int cellsBusy;
/**
* Base counter value, used mainly when there is no contention,
* but also as a fallback during table initialization
* races. Updated via CAS.
*/
private transient volatile long baseCount;
/**
* Table of counter cells. When non-null, size is a power of 2.
* counterCells数组,总数值的分值分别存在每个cell中
*/
private transient volatile CounterCell[] counterCells;
/**
* 注解@sun.misc.Contended用于解决伪共享问题。所谓伪共享,即是在同一缓存行(CPU缓存 的基本单位)中存储了多个变量,当其中一个变量被修改时,就会影响到同一缓存行内的其他变量,导致它们也要跟着被标记为失效,其他变量的缓存命中率将会受到影响。解决伪共享问题的方法一般是对该变量填充一些无意义的占位数据,从而使它独享一个缓存行
*/
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ?Integer.MAX_VALUE :
(int)n);
}
/**
* CounterCell数组的每个元素,都存储一个元素个数,而实际我们调用size方法就是通过这个循环累加来得到的
*/
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
通过上面的代码可以看出元素个数同时存储在成员变量baseCount和counterCell(计数盒子)中,put操作插入后无论是baseCount加1还是counterCell的value加1,都相当于完成了元素总数的加1
接着看addCount
/**
* put完成后调用addCount(1L, binCount);
* long x : 新增元素个数 1
* binCount :桶位上元素个数
*/
private final void addCount(long x, int check) {
// 第一部分 元素总数加x个
CounterCell[] as; long b, s;
/*
* 1. as = counterCells为null时执行 || 后面的逻辑 修改 baseCount
* 1.1 修改失败(其他线程已经更改了baseCount,CAS修改失败) 进入if块
* 1.2 修改成功 完成元素总数的加1操作
*/
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true; // 这个标志fullAddCount方法中再做说明
/*
* 前两个判断:如果计数盒子是空(尚未出现并发)|| 随机取余一个数组位置为空
* true: 说明counterCell还没有创建 直接执行执行fullAddCount(x, true);
* false: 进入第三个判断
* 第三个判断:随机取出counterCell数组的一个节点
* true: 节点没有存值 直接执行执行fullAddCount(x, true);
* false: 节点有值,进入第四个判断进行修改
* 第四个判断:修改counterCell的value值
* 修改成功: 实现了元素总数加1 不执行fullAddCount
* 修改失败: 存在并发,执行fullAddCount(x, false);
*/
if (as == null || (m = as.length - 1) < 0 ||
// ThreadLocalRandom.getProbe() 获取当前线程的探针哈希值,说白了就是获取一个hash值,与上数组长度m,通过简单的散列算法定位一个数组的元素,后面还会用到localInit 初始化和 advanceProbe(h) 更新
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 参数 (1, false或者true)
fullAddCount(x, uncontended);
// 思考:这里提前返回意味着没有检查是否需要扩容,即对比HashMap,ConCurrentHashMap可能有额外的扩容条件
return;
}
// 删除元素时,会调用addCount(-1L, -1); 这个时候无需检查是否扩容直接返回
// 当元素插入的空的桶位时,传进来的check为0,
// 当替换链表的第一个元素的值时,传进来的check为1
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);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
再看fullAddCount方法
/**
* fullAddCount(x, uncontended);
* x : 需要增加元素个数 1
* wasUncontended:未冲突标志
* counterCells未创建 传true
* 上次CAS修改counterCell失败 传false
*/
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 为0则代表该线程的ThreadLocalRandom还未初始化
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization 强制初始化
// 获取线程哈希值 用于counterCells数组寻址
h = ThreadLocalRandom.getProbe();
// addCount方法中尝试了一次修改,失败了才传的false,重新生成哈希值意味着下次取到的counterCell很可能就是另一个,注意:也有可能是同一个
// 未冲突标志设置为true
wasUncontended = true;
}
boolean collide = false;// True if last slot nonempty
// 自旋,很显然又有CAS操作了
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
// counterCells已初始化
if ((as = counterCells) != null && (n = as.length) > 0) {
// 随机取出一个CounterCell元素 等于null 以为意味着数组这个位置的元素没用用过
if ((a = as[(n - 1) & h]) == null) {
// 成员变量cellsBusy相当于AQS中的同步状态,即可以看成是一个锁,获取锁操作就是cas将其从0改为1
// 0表示counterCells不在初始化或者扩容状态下
if (cellsBusy == 0) { // Try to attach new Cell
CounterCell r = new CounterCell(x); // Optimistic create
if (cellsBusy == 0 &&
// 执行成功说明其他线程没有将cellsBusy更改为1,if块中的代码其他线程也进不来
// 执行失败 进入下一次for循环等待其他线程执行完操作将cellsBusy更改为0
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
// 在cellsBusy保护好的代码再次检查counterCell和其长度,防止其他线程在cellsBusy设置为0之前对counterCell进行更改
// 随机取一个数组的元素,如果不为空 又得重新来一次for循环了
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r; // 将创建的CounterCell对象放入寻址位置
created = true;
}
} finally {
cellsBusy = 0; // 释放锁
}
if (created) // created == true 说明操作成功,元素增加操作完成,break 溜之大吉
break;
continue; // Slot is now non-empty // 说明存在竞争该位置已被其他线程放入了CounterCell, 进入下次循环再试,但是下次循环就不一定进得来这里了
}
}
// 设置当前线程的循环失败进行扩容
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// 由于指定下标位置的cell值不为空,则直接通过cas进行原子累加,如果成功,则直接退出
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
// 如果已经有其他线程建立了新的counterCells或者CounterCells大于CPU核心数(线程的并发数不会超过cpu核心数)
else if (counterCells != as || n >= NCPU)
// 设置当前线程的循环失败不进行扩容
collide = false; // At max size or stale
else if (!collide)
// 恢复collide状态,标识下次循环会进行扩容
collide = true;
// 进入这个步骤,说明线程竞争较大, CounterCell数组容量不够,所以先设置cellsBusy 标识为1,表示为正在扩容
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {// Expand table unless stale
// 扩容一倍,数据迁移至新数组的前半段上,后半段为空
CounterCell[] rs = new CounterCell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
// 继续下一次自旋
continue; // Retry with expanded table
}
// 每次自旋操作都会生成新的线程哈希值,这样下次获取的CounterCells的下标大概率就不一样了
h = ThreadLocalRandom.advanceProbe(h);
}
/**
* counterCells的初始化
* 判断1 cellsBusy==0 表示没有在做初始化,通过cas更新cellsbusy的值标注当前线程正在做初始化操作
* 判断2 counterCells == as as之前接收counterCells时为空才会进来这个分支
* 判断3 设置cellsbusy为1,保证分支内的代码只有一个线程执行
*
*/
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try { // Initialize table
// 双重检查,确保counterCells 还是为空
if (counterCells == as) {
// 初始化容量为2
CounterCell[] rs = new CounterCell[2];
// 增加的元素的个数 放在指定的数组下标位置
rs[h & 1] = new CounterCell(x);
counterCells = rs;
// 设置初始化完成标识为true,作为后面跳出自旋的条件
init = true;
}
} finally {
cellsBusy = 0;
}
if (init) // 跳出自旋
break;
}
// 竞争实在是激烈,其它线程占据cell 数组,直接累加在base变量中
// PS:最开始在addCount方法中在counterCells 为null的情况下也尝试去直接累加到baseCount,如果成功就不会进fullAddCount方法了,这反映了在单线程的场景是不会初始化counterCells,直接累加在baseCount变量上
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
有两个疑问暂时搞不懂
- 如果是addCount方法调用fullAddCount方法完成了元素个数增加,那么就会直接return,这就意味着不会去执行检查扩容的操作,那么元素个数达到阈值时触发扩容这个说法是不是有问题呢?
- 在插入到没有元素的桶位时,check参数传0,此时也时直接返回的
if (check <= 1) return;
不检查扩容了?
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 ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
// 以下是检查扩容的代码
...
}
遇到问题阻塞住了,搞明白了再接着看吧。。。。