ConcurrentHashmap基于JDK1.8
参数
// node数组最大容量:2^30=1073741824
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认初始值,必须是2的幕数
private static final int DEFAULT_CAPACITY = 16;
//数组可能最大值,需要与toArray()相关方法关联
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//并发级别,遗留下来的,为兼容以前的版本
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 负载因子
private static final float LOAD_FACTOR = 0.75f;
// 链表转红黑树阀值 > 8 链表转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
//树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))
static final int UNTREEIFY_THRESHOLD = 6;
//最小的红黑树容量
static final int MIN_TREEIFY_CAPACITY = 64;
//每个传输步骤的最小重组次数。范围被细分以允许多个调整大小线程。此值用作下限,以避免调整大小时遇到过多的内存争用。该值应至少为 DEFAULT_CAPACITY。
private static final int MIN_TRANSFER_STRIDE = 16;
//sizeCtl 中用于生成标记的位数。对于 32 位数组,必须至少为 6
private static int RESIZE_STAMP_BITS = 16;
// 2^15-1,help resize的最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 32-16=16,sizeCtl中记录size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// forwarding nodes的hash值 如果hash值等于-1代表线程协助扩容
static final int MOVED = -1; // hash for forwarding nodes
// 树根节点的hash值 如果hash等于-2代表,当前桶是红黑树 s
static final int TREEBIN = -2; // hash for roots of trees
// ReservationNode的hash值
static final int RESERVED = -3; // hash for transient reservations
// 可用处理器数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存放node的数组
transient volatile Node<K,V>[] table;
//下一个要使用的表;仅在调整大小时为非空。
private transient volatile Node<K,V>[] nextTable;
//基本计数器值,主要在没有争用时使用,但也可作为表初始化竞赛期间的后备。通过 CAS 更新。
private transient volatile long baseCount;
//控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
//当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
//当为0时:代表当时的table还没有被初始化
//当为正数时:表示初始化或者下一次进行扩容的大小
private transient volatile int sizeCtl;
//调整大小时要拆分的下一个表索引(加一个)。
private transient volatile int transferIndex;
//调整大小和/或创建 CounterCell 时使用自旋锁(通过 CAS 锁定)。
private transient volatile int cellsBusy;
//如果使用CAS计算失败,也就是说当前处于高并发的情况下,那么
//就会使用CounterCell[]数组进行计数,类似jdk1.7分段锁的形式,锁住一个segment
//最后size()方法统计出来的大小是baseCount和counterCells数组的总和 作者:牛哄哄的java大师
private transient volatile CounterCell[] counterCells;
构造器
//没有维护任何变量的操作,如果调用该方法,数组长度默认是16
public ConcurrentHashMap() {
}
//调用四个参数的构造
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
//传递进来的一个初始化容量,会给予这个值计算一个比这个值大的2的次幂的数作为初始容量
//注意,调用这个方法,得到的初始容量和我们之前讲的HashMap以及jdk7的ConcurrentHashMap不同,即使你传递的是一个2的幂次方数,该方法计算出来的初始容量依然是比这个值大的2的幂次方数
//例如 new ConcurrentHashMap(32); 初始化的容量为64 这与hashmap和1.7的ConcurrentHashMap不同
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
//计算一个大于或者等于给定的容量值,该值是2的幂次方数作为初始容量
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;
}
//基于一个Map集合,构建一个ConcurrentHashMap
//初始容量为16
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
添加元素
put()
put()方法的流程大致跟hashmap相同,不同的地方是,ConcurrentHashMap在添加元素的时候,会针对当前元素所对应的桶位进行加锁操作,保证元素添加的时候,多线程的安全,同时对某个桶位加锁不会影响其他桶位的操作,进一步提升了多线程的并发效率。
多线程协助扩容的操作会在两个地方被触发:
1、当添加元素时,发现添加的元素对用的桶位为fwd节点,就会先去协助扩容,然后再添加元素
2、当添加完元素后,判断当前元素个数达到了扩容阈值,此时发现sizeCtl的值小于0,并且新数组不为空,这个时候,会去协助扩容
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//如果有空值或者空键就抛出异常
if (key == null || value == null) throw new NullPointerException();
//基于key计算hash值,并进行一定的扰动
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();
//如果计算得到的对应数组的下标为空,利用cas将元素添加
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//cas+自旋(和外侧的for构成自旋循环),保证元素添加安全
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//如果hash计算得到的数组下标位置的元素的hash值为MOVED(-1),证明正在扩容,那么就协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//待插入的位置不为空,且当前没有处于扩容操作,就进行元素添加
else {
V oldVal = null;
//对当前数组下的位置进行加锁,保证线程安全,执行添加元素操作
synchronized (f) {
//取出要存储的位置的元素,跟前面取出来的比较
if (tabAt(tab, i) == f) {
//表明是链表结点类型,hash值是大于0的,即spread()方法计算而来
if (fh >= 0) {
//计数器,记录当前位置有几个节点
binCount = 1;
//遍历节点
for (Node<K,V> e = f;; ++binCount) {
K ek;
//如果找到key相同的元素,则用旧值替换新值
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) {
//链表长度大于/等于8,有可能将链表转成红黑树,因为在treeifyBin(tab, i);
//方法中还有一个判断数组长度是否小于64的判断,如果小于64,就不会树化。只是数组扩容。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
//如果旧值不为空,则返回旧值
if (oldVal != null)
return oldVal;
break;
}
}
}
//添加的是新元素,维护集合长度,并判断是否要进行扩容操作
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,说明此时正在初始化,让出cpu
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
//cas修改sizeCtl的值为-1,修改成功,进行数组初始化,失败了,就继续自旋
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//再次确认数组长度是否为空
if ((tab = table) == null || tab.length == 0) {
//sc为0,取默认长度16,否则去sizeCtl的值
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//进行初始化
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
//计算扩容阈值,并赋值sc
sc = n - (n >>> 2);
}
} finally {
//将扩容阈值赋值给sizeCtl
sizeCtl = sc;
}
break;
}
}
return tab;
}
helpTransfer()
//辅助扩容
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
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;
}
addCount()
addCount方法
1、CounterCell数组不为空,优先利用数组中的CounterCell记录数量
2、如果数组为空,尝试对baseCount进行累加,失败后,会执行fullAddCount逻辑
3、如果是添加元素操作,会继续判断是否需要扩容
//计算集合的size
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
//当CounterCell数组不为空,则优先利用数组中的CounterCell记录数量 简称:计数盒子
//或者当baseCount的累加操作失败,会利用数组中的CounterCell记录数量
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
//标识是否有多线程竞争
boolean uncontended = true;
//当as数组为空(尚未出现并发)
//或者当as长度为0
//或者当前线程对应的as数组桶位的元素为空
//或者当前线程对as数组桶位的变量进行修改,但是修改失败(出现并发了)
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
//死循环把x记录到counterCells,uncontended是判断有没有尝试用CAS更新过
fullAddCount(x, uncontended);
return;
}
//到这表示CAS把值记到counterCells肯定成功了,因为上面的是 ||,CAS是最后一个判断条件
//check == 0:表示插入的下标是空的,此时是头节点,此坑位还有不少直接return
//check == 1:表示插入的是链表的第2个,此坑位还有不少,就不考虑扩容,直接return
//check == -1:表示的是删除,直接return
//这里跟hashMap有点不一样,没有直接判断大于多少就扩容
if (check <= 1)
return;
//把counterCells的值累加到baseCount
s = sumCount();
}
//check>=0表示需要进行扩容操作
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
//当元素个数达到扩容阈值
//并且数组不为空
//并且数组长度小于限定的最大值
//满足以上所有条件,执行扩容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//根据 length 得到一个标识
int rs = resizeStamp(n);
//sizeCtl小于0,说明正在执行扩容,那么协助扩容
if (sc < 0) {
//扩容结束或者扩容线程数达到最大值或者扩容后的数组为null或者没有更多的桶位需要转移,结束操作
// 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
// 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc == rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
// 如果 nextTable == null(结束扩容了)
// 如果 transferIndex <= 0 (转移状态变化了)
// 结束循环
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//扩容线程加1,成功后,进行协助扩容操作
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
//协助扩容,newTable不为null
transfer(tab, nt);
}
//没有其他线程在进行扩容,达到扩容阈值后,给sizeCtl赋了一个很大的负数
//1+1=2 --》 代表此时有一个线程在扩容
//rs << RESIZE_STAMP_SHIFT 是一个很大的负数
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
//扩容,newTable为null
transfer(tab, null);
s = sumCount();
}
}
}
fullAddCount()
fullAddCount方法
1、当CounterCell数组不为空,优先对CounterCell数组中的CounterCell的value累加
2、当CounterCell数组为空,会去创建CounterCell数组,默认长度为2,并对数组中的CounterCell的value累加
3、当数组为空,并且此时有别的线程正在创建数组,那么尝试对baseCount做累加,成功即返回,否则自旋
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
//获取当前线程hash值
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
//把标志位置为true,确保为true,也就是确保就是没有CAS过
wasUncontended = true;
}
//标识是否有冲突,如果最后一个桶不是null,那么为true
boolean collide = false; // True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
//数组不为空,优先对数组中CouterCell的value累加
if ((as = counterCells) != null && (n = as.length) > 0) {
//线程对应的桶位不为null
if ((a = as[(n - 1) & h]) == null) {
//cellsBusy为0,表示没线程在初始化或者扩容counterCells
if (cellsBusy == 0) { // Try to attach new Cell
//创建CounterCell对象
CounterCell r = new CounterCell(x); // Optimistic create
//利用CAS修改cellBusy状态为1,,说明线程在用
//成功则将刚才创建的CounterCell对象放入数组中
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
//桶位为空, 将CounterCell对象放入数组
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
//表示放入成功
created = true;
}
} finally {
//最终将cellsBusy置为0
cellsBusy = 0;
}
//成功退出循环
if (created)
break;
//桶位已经被别的线程放置了已给CounterCell对象,继续循环
continue; // Slot is now non-empty
}
}
collide = false;
}
//桶位不为空,重新计算hash值,然后继续循环
else if (!wasUncontended) // CAS already known to fail
//进到里面,说明之前在addCount已经CAS过了,并且失败了
//第一次只把标志位改为true,改个状态出去,给个机会,第二次再来CAS,避免竞争
wasUncontended = true; // Continue after rehash
//走到这,2种可能
//1:是死循环的第二次了
//2:是之前没有CAS过
//不管哪种情况,必须要CAS了,要么是第一次,要么已经让过一次了,只能竞争了。
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
//数组被别的线程改变了,或者数组长度超过了可用cpu大小
//重新计算线程hash值,否则继续下一个判断
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
//当没有冲突,修改为有冲突,并重新计算线程hash,继续循环
else if (!collide)
//进到里面,说明需要扩容了
collide = true;
//如果CounterCell的数组长度没有超过cpu核数,对数组进行两倍扩容
//并继续循环
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
}
//每次不成功就算下新的hash,换个下标试试
h = ThreadLocalRandom.advanceProbe(h);
}
//CounterCell数组为空,并且没有线程在创建数组,修改标记,并创建数组
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try { // Initialize table
if (counterCells == as) {
//初始化长度为2
CounterCell[] rs = new CounterCell[2];
rs[h & 1] = new CounterCell(x);
counterCells = rs;
//设置初始化状态init为true
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
//数组为空,并且有别的线程在创建数组,那么尝试对baseCount做累加,成功就退出循环,失败就继续循环
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
transfer()
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//如果是多cpu,那么每个线程划分任务,最小任务量是16个桶位的迁移
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//如果是扩容线程,此时新数组为null
//如果不是扩容线程,此时数组不为空。即表示当前线程是协助扩容的线程
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;
}
//记录新数组的长度
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记录下一次任务迁移的开始桶位
for (int i = 0, bound = 0;;) {
//f 桶位的头结点
//fh 头结点的hash
Node<K,V> f; int fh;
while (advance) {
//分配任务的开始下标
//分配任务的结束下标
int nextIndex, nextBound;
//--i >= bound 成立表示当前线程分配的迁移任务还没有完成
if (--i >= bound || finishing)
advance = false;
//没有元素需要迁移了 后续会去将扩容线程数减1,并判断扩容是否完成
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
//计算下一次任务迁移的开始桶位,并将这个值赋值给transferIndex
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
//如果没有更多的需要迁移的桶位
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//扩容结束后,保存新数组,并重新计算扩容阈值,赋值给sizeCtl
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
//扩容任务线程数减1
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
}
}
//当前迁移的桶位没有元素,直接在该位置添加一个fwd节点
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
//当前节点已经被迁移,当前线程不用再处理了
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
//当前桶位有数据,而且node节点 不是 fwd节点,说明这些数据需要迁移。
else {
//当前节点需要迁移,加锁迁移,保证多线程安全
synchronized (f) {
//f表示当前位置的那个元素
if (tabAt(tab, i) == f) {
//ln 表示低位链表引用
//hn 表示高位链表引用
Node<K,V> ln, hn;
//表示当前桶位是链表桶位
if (fh >= 0) {
//fh表示f的hash值
//n表示原数组的长度,数组长度都是2的n次方,默认长度为16转为二进制为10000
//fh & n的结果就只有两种,要么是 0 要么是 n ,也就是说要么是 00000 ,要么是 10000
int runBit = fh & n;
//lastRun表示最后一个发生变化的节点。在这个节点之后的节点不需要动
Node<K,V> lastRun = f;
//找出最后一段完整的fh&n不变的链表,这样最后这一段链表就不用重新创建新结点了
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;
}
//lastRun之前的结点因为fh&n不确定,所以全部需要重新迁移。
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);
}
//低位链表放在i处
setTabAt(nextTab, i, ln);
//高位链表放在i+n处
setTabAt(nextTab, i + n, hn);
//在原table中设置ForwardingNode节点以提示该桶扩容完成。
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;
}
}
}
}
}
}
删除元素
remove()
public V remove(Object key) {
return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//删除时也需要确实扩容完成后才可以操作
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
//cv不为null则替换,否则是删除
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
//没前置节点就是头节点
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
ConcurrentHashmap基于JDK1.7
Segment数组的长度是不可以被改变的,初始化如果不规定,那么就采用默认的 2^4
ConcurrentHashMap中保存了一个默认长度为16的Segment[],每个Segment元素中保存了一个默认长度为2的HashEntry[],我们添加的元素,是存入对应的Segment中的HashEntry[]中。所以ConcurrentHashMap中默认元素的长度是32个,而不是16个
参数
ConcurrentHashMap 类
// 默认初始容量是16, 和 HashMap 一样
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认加载因子是 0.75, 和 HashMap 一样
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 默认并发级别时16,这个并发级别决定了Segment数组的长度
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 每个Segment的table的最小容量
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
// 最多能有多少个segment
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
// 尝试对整个map进行操作(比如说统计map的元素数量),可能需要锁定全部segment;
// 这个常量表示锁定所有segment前,尝试的次数
static final int RETRIES_BEFORE_LOCK = 2;
// 用于确定哪一个Segment的掩码值,hash的高几位用于选择Segment
final int segmentMask;
// 确定哪一个Segment的时候 hash偏移的位数
final int segmentShift;
// 最重要的属性,每个Segment可以看出一个Hash表,键值对是存在Segment中的
final Segment<K,V>[] segments;
......
Segment 类
// 真正存储数据的数组,每个Segment独自有一个 table 存储键值对
transient volatile HashEntry<K,V>[] table;
// table 中元素的个数
transient int count;
// table 修改的次数
transient int modCount;
// 扩容阈值,它等于 capacity * loadFactor
// capacity 就是数组 table 的长度
transient int threshold;
// 负载因子,每个Segment都相同
final float loadFactor;
......
Segment 内部自己维护一个Hash表,它有自己的扩容阈值和负载因子,负载因子每个Segment 都相同,之后扩容的时候是每个Segment自己扩容,不会影响到 ConcurrentHashmap 其他的 Segment。同时,可以看出Segment 是继承自 ReentrantLock 接口的,所以相当于每个Segment自己有一把锁,想要对Segment进行修改的时候需要先得到这个锁。
构造器
//通过指定的容量,加载因子和并发等级创建一个新的ConcurrentHashMap
@SuppressWarnings("unchecked")
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//对容量,加载因子和并发等级做限制
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//限制并发等级不可以大于最大等级
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
// 下面即通过并发等级来确定Segment的大小
//sshift用来记录向左按位移动的次数
int sshift = 0;
//ssize用来记录Segment数组的大小
int ssize = 1;
//Segment的大小为大于等于concurrencyLevel的第一个2的n次方的数
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
//segmentMask的值等于ssize - 1(这个值很重要)
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//c记录每个Segment上要放置多少个元素
int c = initialCapacity / ssize;
//假如有余数,则Segment数量加1
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
//创建第一个Segment,并放入Segment[]数组中,作为第一个Segment
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
putAll(m);
}
添加元素
put()
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
//计算key的哈希值
int hash = hash(key);
//因为一个键要计算两个数组的索引,为了避免冲突,这里取高位计算Segment[]的索引
int j = (hash >>> segmentShift) & segmentMask;
//判断该索引位的Segment对象是否创建,没有就创建
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
//调用Segmetn的put方法实现元素添加
return s.put(key, hash, value, false);
}
ensureSegment()
//创建对应索引位的Segment对象,并返回
private Segment<K,V> ensureSegment(int k) {
// 临时段表。
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
// 注意,这里是getObjectVolatile这个方法,这个方法的意思就是,别的线程要是修改了segment[k],这个线程是可以看到操作后的结果
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
// 这里看到为什么之前要初始化 segment[0] 了,
// segment[0] 就相当于一个初始化模板,
// 使用当前 segment[0] 处的数组长度和负载因子来初始化 segment[k],
// 为什么要用“当前”,因为 segment[0] 可能早就扩容过了。
// 获取当前 segment[0] 作为初始化模板
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
// 初始化 segment[k] 内部的哈希表。
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
// 再次检查一遍该槽是否被其他线程初始化了。
// 也就是在做上面那些操作时,看看是否有别的线程操作过 segment[k]。
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
// 构建新的 segment 对象。
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
// 再次检查 segment [k] 是否为null。
// 注意,这里是while,之前的if,也是起到如果下面操作失败,再次检查的作用。
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
// CAS操作,这里的比较并交换在CPU里面就是一条指令,保证原子性的。
// 不存在那种比较完毕之后的间隙,突然切换到别的线程来修改这个值的情况。
break;
}
}
}
// 返回 segment 对象。
// 这里返回的seg可能是自己new的,也可能是别的线程new的,反正只要其中一个就好了。
return seg;
}
Segment.put()
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 在 put 到指定段中之前,我们得获取到当前段表中这个桶的独占的锁。
// 以此来保证整个过程,只有我们一个线程在对这个桶做操作。
HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
//用来存储被覆盖的值
V oldValue;
try {
// 这个是段表某个桶内部的哈希表。
HashEntry<K,V>[] tab = table;
// 再次利用待插入键值对 key 的 hash 值,求应该放置的数组下标。
int index = (tab.length - 1) & hash;
// first 是桶中哈希表的待插入桶处的链表的表头。
HashEntry<K,V> first = entryAt(tab, index);
// 遍历链表
for (HashEntry<K,V> e = first;;) {
// 如果不为空,就去判断是否存在该节点,存在就覆盖
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
// node 到底是不是 null,这个要看获取锁的过程,不过和这里都没有关系。
// 如果不为 null
if (node != null)
//将它设置为头节点。JDK1.7这里使用的是头插法
node.setNext(first);
else
//不为空,就将它初始化再插入
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
// 如果超过了段表中该桶中哈希表的阈值,这个哈希表就需要扩容。
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
//扩容
rehash(node);
else
// 没有达到阈值,将 node 放到哈希表的 index 位置,
// 其实就是将新的节点设置成原链表的表头,使用头插法。
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
//释放锁
unlock();
}
return oldValue;
}
scanAndLockForPut()
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
// 简单来说就是拿到段表中某个桶中的哈希表数组中通过hash计算的那个下标下的第一个节点。
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
// 用于记录获取锁的次数。
int retries = -1; // negative while locating node
// 循环获取锁。
// 如果获取失败,就会去进行一些准备工作。
while (!tryLock()) {
// 辅助变量用于重复检查,
// 用来检查对应段表中那个桶上的哈希表数组中对应索引桶处,之前取出来的第一个节点是否还是我们之前取得那个。
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
// 判断段表中对应桶中的哈希表的对应桶上的节点 HashEntry 是不是还没被初始化。
if (e == null) {
if (node == null) // speculatively create node
// 进到这里说明数组该位置的链表是空的,没有任何元素。
// 当然,进到这里的另一个原因是 tryLock() 失败,所以该槽存在并发,不一定是该位置。
// 将我们即将插进去的元素,构建成一个HashEntry节点对象。
node = new HashEntry<K,V>(hash, key, value, null);
// 将 retries 赋值为0,不让准备工作重复执行。
retries = 0;
}
// 否则的话,判断 key 是否有重复的
else if (key.equals(e.key))
// 将 retries 赋值为0,不让准备工作重复执行。
retries = 0;
else
// 否则顺着链表往下走
e = e.next;
}
// 重试次数如果超过 MAX_SCAN_RETRIES(单核1多核64),那么不抢了,进入到阻塞队列等待锁,避免cpu空转。
// lock() 是阻塞方法,直到获取锁后返回。
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
// 偶数次数才进行后面的判断。
// 这个时候出现问题了,那就是有新的元素进到了链表,成为了新的表头。
// 也可以说是链表的表头被其他线程改变了。
// 所以这边的策略是,相当于重新走一遍这个 scanAndLockForPut 方法。
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
// 此时怎么做呢,
// 别的线程修改了该segment的节点,重新赋值e和first为最初值,和第一二行代码一样的效果。
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
// 将准备工作制作好的节点返回。
return node;
}
rehash()
// 传入的参数 node 是这次扩容后,需要添加到新的数组中的数据。
private void rehash(HashEntry<K,V> node) {
// 用来存储待扩容槽中的哈希表旧表
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
// 新的容量为旧容量的 2 倍
int newCapacity = oldCapacity << 1;
// 设置新的扩容阈值
threshold = (int)(newCapacity * loadFactor);
// 用上面的参数创建新的哈希表
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
// 新的掩码,如从 16 扩容到 32,那么 sizeMask 为 31,对应二进制 000...00011111
int sizeMask = newCapacity - 1;
// 遍历原数组,将原哈希表索引 i 处的链表拆分到新哈希表索引 i 和 i+oldCap 两个位置,高位和低位。
for (int i = 0; i < oldCapacity ; i++) {
// e 是链表的第一个元素。
HashEntry<K,V> e = oldTable[i];
// 如果这条链不是空链的话
if (e != null) {
HashEntry<K,V> next = e.next;
// 计算当前节点应该放置在新数组中的位置,
// 假设原哈希表长度为 16,e 在 oldTable[3] 处,那么 e 在新哈希表中的索引 idx 只可能是 3 或者是 3 + 16 = 19。
int idx = e.hash & sizeMask;
// 该位置处只有一个元素
if (next == null) // Single node on list
// 直接将该节点设置到这里
newTable[idx] = e;
else { // Reuse consecutive sequence at same slot
// e 是链表表头
HashEntry<K,V> lastRun = e;
// idx 是当前链表的头结点 e 的新位置
int lastIdx = idx;
// 下面这个 for 循环会找到一个 lastRun 节点,这个节点之后的所有元素是将要放到一起的。
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
// 将 lastRun 及其之后的所有节点组成的这个链表放到 lastIdx 这个位置
newTable[lastIdx] = lastRun;
// Clone remaining nodes
// 下面的操作是处理 lastRun 之前的节点,
// 这些节点可能分配在另一个链表中,也可能分配到上面的那个链表中
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
// 将新来的 node 放到新数组中刚刚的 两个链表之一 的 头部
int nodeIndex = node.hash & sizeMask; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
}
删除元素
remove()
public V remove(Object key) {
// 得到待删除的key的哈希值
int hash = hash(key);
// 根据hash确定segment
Segment<K,V> s = segmentForHash(hash);
// 调用segment.remove进行删除
return s == null ? null : s.remove(key, hash, null);
}
final V remove(Object key, int hash, Object value) {
// 获取锁失败,则不断自旋尝试获取锁
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
// 定位到segment中table的哪个位置
int index = (tab.length - 1) & hash;
HashEntry<K,V> e = entryAt(tab, index);
HashEntry<K,V> pred = null;
// 遍历链表
while (e != null) {
K k;
HashEntry<K,V> next = e.next;
// 如果key和hash都匹配
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
// 如果没有传入value,则直接删除该节点
// 如果传入了value,比如调用的map.remove(key,value),则要value匹配才会删除,否则不操作
if (value == null || value == v || value.equals(v)) {
// 头结点就是要找删除的元素,next为null,则将null赋值数组的该位置
if (pred == null)
setEntryAt(tab, index, next);
else
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
}
// 不匹配时,pred保存当前一次检测的节点,e指向下一个节点
pred = e;
e = next;
}
} finally {
// 释放锁
unlock();
}
return oldValue;
}
总结
JDK7中的ConcurrentHashMap是怎么保证并发安全
主要利用Unsafe操作+ReentrantLock+分段思想。
主要使用了Unsafe操作中的:
1. compareAndSwapObject:通过cas的方式修改对象的属性
2. putOrderedObject:并发安全的给数组的某个位置赋值
3. getObjectVolatile:并发安全的获取数组某个位置的元素
分段思想是为了提高ConcurrentHashMap的并发量,分段数越高则支持的最大并发量越高,程序员可以通过concurrencyLevel参数来指定并发量。ConcurrentHashMap的内部类Segment就是用来表示某一个段的。
每个Segment就是一个小型的HashMap的,当调用ConcurrentHashMap的put方法是,最终会调用到Segment的put方法,而Segment类继承了ReentrantLock,所以Segment自带可重入锁,当调用到Segment的put方法时,会先利用可重入锁加锁,加锁成功后再将待插入的key,value插入到小型HashMap中,插入完成后解锁。
JDK7中的ConcurrentHashMap的底层原理
ConcurrentHashMap底层是由两层嵌套数组来实现的:
1.ConcurrentHashMap对象中有一个属性segments,类型为Segment[];
2.Segment对象中有一个属性table,类型为HashEntry[];
当调用ConcurrentHashMap的put方法时,先根据key计算出对应的Segment[]的数组下标j,确定好当前key,value应该插入到哪个Segment对象中,如果segments[j]为空,则利用自旋锁的方式在j位置生成一个Segment对象。
然后调用Segment对象的put方法。
Segment对象的put方法会先加锁,然后也根据key计算出对应的HashEntry[]的数组下标i,然后将key,value封装为HashEntry对象放入该位置,此过程和JDK7的HashMap的put方法一样,然后解锁。
在加锁的过程中逻辑比较复杂,先通过自旋加锁,如果超过一定次数就会直接阻塞等等加锁。
JDK8中的ConcurrentHashMap是怎么保证并发安全
主要利用Unsafe操作+synchronized关键字。
Unsafe操作的使用仍然和JDK7中的类似,主要负责并发安全的修改对象的属性或数组某个位置的值。
synchronized主要负责在需要操作某个位置时进行加锁(该位置不为空),比如向某个位置的链表进行插入结点,向某个位置的红黑树插入结点。
JDK8中其实仍然有分段锁的思想,只不过JDK7中段数是可以控制的,而JDK8中是数组的每一个位置都有一把锁。
当向ConcurrentHashMap中put—个key,value时,
1.首先根据key计算对应的数组下标i,如果该位置没有元素,则通过自旋的方法去向该位置赋值。
2.如果该位置有元素,则synchrpnized会加锁
3.加锁成功之后,在判断该元素的类型
a.如果是链表节点则进行添加节点到链表中
b.如果是红黑树则添加节点到红黑树
4.添加成功后,判断是否需要进行树化
5.addCount,这个方法的意思是ConcurrentHashMap的元素个数加1,但是这个操作也是需要并发安全的,并且元素个数加1成功后,会继续判断是否要进行扩容,如果需要,则会进行扩容,所以这个方法很重要。
6.同时一个线程在put时如果发现当前ConcurrentHashMap正在进行扩容则会去帮助扩容。
ConcurrentHashMap 1.7 和1.8的区别
实现上的区别:
JDK1.7使用的 Segment + HashEntry + Unsafe
JDK1.8使用的 Synchronized + CAS + Node + Unsafe
put()方法的区别:
JDK1.7
1、需要定位 2 次 (segments[i],segment中的table[i]),也就是两次hash
2、没获取到 segment 锁的线程,没有权力进行put操作,而是会去做下一个put操作的准备工作
JDK1.8:
先拿到根据 rehash值 定位,拿到 table[i]的 首节点first,然后:
如果为 null ,通过 CAS 的方式把 value put进去
如果 非null ,并且 first.hash == -1 ,说明其他线程在扩容,参与一起扩容
如果 非null ,并且 first.hash != -1 ,Synchronized锁住 first 节点,判断是链表还是红黑树,遍历插入。
扩容的方式:
JDK1.7
跟HashMap的 resize() 没太大区别,都是在 put() 元素时去做的扩容,所以在1.7中的实现是获得了锁之后,在单线程中去做扩容,先new个2倍数组再去遍历old数组节点搬去新数组。
JDK1.8
jdk1.8的扩容支持并发迁移节点,从old数组的尾部开始,如果该桶被其他线程处理过了,就创建一个 ForwardingNode 放到该桶的首节点,hash值为-1,其他线程判断hash值为-1后就知道该桶被处理过了。
计算size
JDK1.7
先采用不加锁的方式,计算两次,如果两次结果一样,说明是正确的,返回。
如果两次结果不一样,则把所有 segment 锁住,重新计算所有 segment 的 Count 的和
JDK1.8
用一个 baseCount 变量来记录 ConcurrentHashMap 当前 节点的个数。
先尝试通过CAS 修改 baseCount
如果多线程竞争激烈,某些线程CAS失败,那就CAS尝试将 CELLSBUSY 置1,成功则可以把 baseCount变化的次数 暂存到一个数组 counterCells 里,后续数组 counterCells 的值会加到 baseCount 中。
如果 CELLSBUSY 置1失败又会反复进行CAS baseCount 和 CAScounterCells 数组