ConcurrentHashMap(以下简称CHM)的计数比较有特色,跟很多实现不同的是,CHM并没有一个专门的属性数据,在实现上也没有用锁来同步。那么它到底是怎么实现的呢?
CHM计数的总体原理
CHM在实现计数时,主要借助的是baseCount和counterCells(数组)两个属性。
- baseCount是原生类型long,默认值0L
- baseCount在首次添加元素的时候设置为1
- 视并发情况的不同,可能在addCount中赋值,也可能在fullAddCount中赋值,且仅赋值一次
- counterCells数组的长度也是2的幂
- 每个线程根据其probe的值获取对应的counterCells下标
- 每个线程只会更新对应数组元素中的数据
- 求size时,就是baseCount + counterCells计数之和
【注意1】counterCells之所以不用基本类型(Long,Integer等)计数,而是用CounterCell对象,是因为要用volatile。而volatile修饰数组,只对数组的引用有效,而对数组中的元素无效。所以才用数组存储CounterCell对象,而在CounterCell对象中用volatile计数。
【注意2】CounterCell类声明时使用了@sun.misc.Contended注解,避免伪共享。该注解会在对象的前后各添加128字节,以避免同临近数据一起被加入到同一L1缓存行中。
源码
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 如果计数数组未初始化,就设置baseCount
// 如果已初始化,或初始化时设置baseCount“竞争”失败,
// 则通过计数数组计数
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();
}
if (check >= 0) {
// 如下扩容部分暂且省略,留待讲扩容的时候再讲
。。。
}
// x:当前线程计数的增加值
// wasUncontended:线程计数竞争counterCell时是否失败。其实调用fullAddCount时必然竞争失败,
// 所以wasUnconteded肯定是false
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
// 初始化当前线程的探针,也就是线程的HashCode。为了避免冲突,线程的探针可以修改,通过advanceProbe方法
if ((h = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // force initialization
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
// 如果counterCells已经初始化过
if ((as = counterCells) != null && (n = as.length) > 0) {
// 且当前线程的counterCell未使用过
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;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
else if (!collide)
collide = true;
// 尝试扩容counterCells
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
}
// 如果目标counterCell已被占用,且设置失败,也在竞争扩容时失败,则修改当前线程的PROBE
h = ThreadLocalRandom.advanceProbe(h);
}
// 尝试初始化counterCells
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;
}
// counterCells未初始化,当前线程也在竞争初始化的机会时失败,则设置baseCount。
// 这是第2/2次设置baseCount的机会
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
// CHM的size直接调用的是sumCount,只是对结果最后又做了一层校验而已。
// sumCount其实就是baseCount + 各个counterCell里的值
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;
}
本文深入探讨了ConcurrentHashMap(CHM)独特的计数机制,包括baseCount与counterCells的作用及其实现细节。介绍了线程如何通过probe值定位counterCells进行高效并发计数,并解析了CHM在不同并发情况下如何通过CAS操作保证计数正确性。
1657

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



