前言
最近在学习ConcurrentHashMap的源码,发现它采用了一种比较独特的方式对map中的元素数量进行统计,自然是要好好研究一下其原理思想,同时也能更好地理解ConcurrentHashMap本身。
本文主要思路分为以下5个部分
1.计数的使用效果
2.原理的直观图解
3.源码的细节分析
4.与AtomicInteger的比较
5.思想的抽象
学习的入口自然是map的put方法
public V put(K key, V value) {
return putVal(key, value, false);
}
查看putVal方法
这里并不对ConcurrentHashMap本身的原理作过多讨论,因此我们直接跳到计数部分
final V putVal(K key, V value, boolean onlyIfAbsent) {
...
addCount(1L, binCount);
return null;
}
每当成功添加一个元素之后,都会调用addCount方法进行数量的累加1的操作,这就是我们研究的目标
因为ConcurrentHashMap的设计初衷就是为了解决多线程并发场景下的map操作,因此在作数值累加的时候自然也要考虑线程安全
当然,多线程数值累加一般是学习并发编程的第一课,本身并非很复杂,可以采用AtomicInteger或者锁等等方式来解决该问题
然而如果我们查看该方法,就会发现,一个想来应该比较简单的累加方法,其逻辑看上去却相当复杂
这里我只贴出了累加算法的核心部分
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();
}
...
}
我们就来研究一下该逻辑的实现思路。而这个思路其实是照搬了LongAdder类的逻辑,因此我们直接查看该算法的原始类
1.LongAdder类的使用
我们先看下LongAdder的使用效果
LongAdder adder = new LongAdder();
int num = 0;
@Test
public void test5() throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread((<