最近,在写一个数据监控项目,涉及到ConcurrentHashMap,之前只是用,虽然没出问题,但是并不是特别清楚里面的原理,所以,这一次,需要把原理弄明白再开始动手。
==============================================
首先,如果输入
map = new ConcurrentHashMap(512);
我们看初始化的部分:
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; }
看图走的分支
接下来,计算下tableSizeFor的内容,其中入参为(512+(256)+1)--->769
我们先看tableSizeFor的函数声明
/** * Returns a power of two table size for the given desired capacity. * See Hackers Delight, sec 3.2 */
那么,很简单了,769的输出就是1024,所以总体来说,我们一开始参数为512,输出结果为1024.
所以,后面可以考一个面试题,当ConcurrentHashMap的初始化参数为512时,实际的桶个数为多少?
好,接下来,我们继续去执行我们的业务代码。
这里主要使用了merge函数,
private static ConcurrentHashMapGLOBAL_MAP = null;GLOBAL_MAP = new ConcurrentHashMap (256);GLOBAL_MAP.merge(key, report, GLOBAL_BI_FUNCTION);
进入merge函数查看代码
int h = spread(key.hashCode());
先计算key.hashCode
算法比较简单,每个String会计算一个哈希值,如果之前计算过了,会缓存起来。
---
static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; }
这个就是根据上面的值计算一个新的值。
其中HASH_BITS的值为
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
---继续执行
进来,因为之前没有初始化过table,所以这里需要初始化,我们看看初始化的代码。
执行完后,table就初始化完毕了。
---
然后就执行上面的这行语句。
---
其中n是哈希表的长度,(n-1)&h就是根据哈希值与上桶的总长度,得出一个桶的下标。
这个就不解释了,跟C中的数组一样的原理,现在我们取到了一个桶。
---
上图中,因为刚开始所有的数组元素都为空,所以需要初始化这个数组里的元素不为空。
---进入casTabAt函数,就是给这个空元素赋予一个Node值。
接下来执行
if (delta != 0) addCount((long)delta, binCount);
这里的delta值为1,所以会执行addCount函数,第一次执行时没有特别的地方,
---接着执行第2次merge操作,如果这次也是执行同一个桶的的操作的话,
可以看到,会锁住这个对象,也就是锁住桶。
下面看看如何降低这个锁粒度的冲突。
f 如果要一样--->h要一样--->key要一样--->结论:同一个key会锁住同一个桶。
---接下来执行
这里就开始执行我们的函数了。
最后看一下自定义的remappingFunction函数的执行条件
哈希值相同,并且(要么key是同一个对象,要么key.equals相同),就认为是找到了这个对象,可以执行merge操作了。