# 分段锁设计提高统计元素数量的性能

``````final V putVal(K key, V value, boolean onlyIfAbsent) {
//省略部分代码....
return null;
}``````

# size计数的基本原理分析

• 当线程竞争不激烈时，直接对`baseCount+1`来增加元素个数。
• 当线程竞争比较激烈时，通过构建一个`CounterCell`数组，默认长度是2，然后通过随机算法选择一个`CounterCell`，针对该`CounterCell`中的value进行保存。
``````private transient volatile long baseCount;
private transient volatile CounterCell[] counterCells;``````

• 对ConcurrentHashMap中元素个数进行累加。
• 通过`check>=0`来判断是否需要扩容，这部分代码在前面分析`tranfer()`方法中有讲过，就不再赘述。
``````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))) {
return;
}
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();
}
}
}``````

``````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))) {
return;
}
if (check <= 1)
return;
s = sumCount();
}
}``````

• 在if判断中，第一步就是先通过`U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)`修改全局成员变量baseCount进行累加。这个方法在线程竞争的情况下会返回false。
• 如果第一步执行失败，则尝试使用`CounterCell`进行累加，这里面有几个逻辑。

• 在if判断中通过`as=counterCells`把当前用来记录元素个数的全局变量`counterCells`赋值给了as。
• 在第二个if判断中，有几个具体的判断逻辑，这些判断逻辑任何一个为true，都会调用`fullAddCount`方法去实现元素个数累加。判断逻辑简单说明一下。

• `as==null` 说明`CounterCell`数组还未初始化。
• `(m = as.length - 1) < 0` ，这个判断理论上来说不存在，因为一旦`CounterCell`被初始化，就意味着`as.length`长度是2，所以不可能出现结果小于0的情况，如果各位读者有其他理解可以反馈给笔者。
• `(a = as[ThreadLocalRandom.getProbe() & m]) == null`说明`CounterCell`数组已经创建了，但是通过探针hash定位在数组中中没有对象实例，说明这个数组中还存在没有`CounterCell`实例对象的情况。
• `U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)`执行到了这个判断逻辑，说明当前`CounterCell`数组每个位置都有一个`CounterCell`实例对象，直接通过CAS操作针对上一个步骤获取到的`CounterCell`的value值进行累加，如果失败，说明存在竞争。
• `sumCount();`返回总的元素个数，实际上应该就是`CounterCell`数组和`baseCount`两者累加计算的结果。

• 如果`CounterCell`数组还未初始化，则先初始化。
• 如果已经初始化，则随机找到其中一个值进行累加更新。
• 如果线程竞争加剧，则会尝试对`CounterCell`数组进行扩容。

## 第一个部分，初始化CounterCell

`CounterCell`数组初始化处理不难理解。

• `cellsBusy == 0 && counterCells == as &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)`这个部分，是通过CellsBusy字段来表示抢占到锁的标记，通过CAS修改CellsBusy=1来表示占有状态。
• ` CounterCell[] rs = new CounterCell[2];`构造一个长度为2的`CounterCell`数组。
• `rs[h & 1] = new CounterCell(x);`，把当前增加的元素个数`x`保存到`rs[h&1]`的位置。
• `counterCells = rs;``rs`赋值给全局对象`counterCells`
``````private final void fullAddCount(long x, boolean wasUncontended) {
//省略部分代码....
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
if ((as = counterCells) != null && (n = as.length) > 0) {
//省略部分代码....
}
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try {                           // Initialize table
if (counterCells == as) {
CounterCell[] rs = new CounterCell[2];
rs[h & 1] = new CounterCell(x);
counterCells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
}
}``````

## 第二个部分，增加元素个数

• `(as = counterCells) != null && (n = as.length) > 0`表示`counterCells`数组已经完成了初始化。
• `CounterCell r = new CounterCell(x);`先创建一个`CounterCell`对象，把`x`保存进去。
• `U.compareAndSwapInt(this, CELLSBUSY, 0, 1)`当前线程占用锁。
• `rs[j] = r`，把新构建的保存了元素个数`x``CounterCell`对象保存到`rs[j]`的位置。
``````private final void fullAddCount(long x, boolean wasUncontended) {
//省略部分代码....
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
if ((as = counterCells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) {            // Try to attach new Cell
CounterCell r = new CounterCell(x); // Optimistic create
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try {               //Recheck under lock
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue;           // Slot is now non-empty
}
}
collide = false;
}
//省略部分代码....
}
//省略部分代码....
}
}``````

``````private final void fullAddCount(long x, boolean wasUncontended) {
//省略部分代码....
for (;;) {
//由于指定下标位置的cell值不为空，则直接通过cas进行原子累加，如果成功，则直接退出
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))//
break;
}
//省略部分代码....
}``````

## 第三个部分，CounterCell数组扩容

• `cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)`抢占锁。
• `CounterCell[] rs = new CounterCell[n << 1];`在原有的基础上扩容一倍，再通过`for`循环进行数据迁移。
• `counterCells = rs;`把扩容后的对象赋值给`counterCells`
``````private final void fullAddCount(long x, boolean wasUncontended) {
//省略代码....
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {// Expand table unless stale
//扩容一倍 2变成4
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;//继续下一次自旋
}
//省略部分代码....
}``````

# size()方法结果汇总

``````public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
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`的值，保存到`sum`这个字段中。
• 遍历`CounterCell`数组，把数组中每个`CounterCell`中存储的value进行累加。

##### 跟着Mic学架构

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始...

322 声望
800 粉丝
0 条评论