ConcurrentHashMap中并发级别,桶内hash表的长度,扩容门槛的确定

来源:互联网 发布:苏沉船知乎 编辑:程序博客网 时间:2024/05/22 12:49

ConcurrentHashMap中sigment数量,sigment中hash表的长度,以及加载因子的确定。
分析一下下面的构造函数参数
initialCapacity 是我们指定的map的初始容量大小,但是map初始完成后的容量并不一定等于该值。
loadFactor 用来控制每个桶内的扩容的门槛threshold 桶内元素大于该值,要扩容在hashrehash()
concurrencyLevel 并发的级别,用来间接指定map中通的数量。
上面参数的具体使用,看一下下面的具体代码的注释部分。

1static final int MAXIMUM_CAPACITY = 1 << 30;    (2static final int DEFAULT_CONCURRENCY_LEVEL = 16;    (3static final int MAX_SEGMENTS = 1 << 16; // slightly conservative public ConcurrentHashMap(int initialCapacity,                             float loadFactor, int concurrencyLevel) {        //合法性检查loadFactor 装填因子是小于1大于0的。threshold是根据hash表的长度*loadFactor计算所得        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)            throw new IllegalArgumentException();        //并发量不得大于MAX_SEGMENTS,见函数上面的值(3)        if (concurrencyLevel > MAX_SEGMENTS)            concurrencyLevel = MAX_SEGMENTS;        // Find power-of-two sizes best matching arguments        int sshift = 0;        int ssize = 1;        //桶的数量是比我们指定的并发数量concurrencyLevel大或者相等的最小的2的n次幂。        while (ssize < concurrencyLevel) {            ++sshift;            ssize <<= 1;        }        //通过元素的hash值定位所在桶时用到的掩码,结合下面segmentFor函数,可以得至,在定位桶时,是使用了,hash值得高sshift位来计算的。        segmentShift = 32 - sshift;        segmentMask = ssize - 1;        //初始化桶        this.segments = Segment.newArray(ssize);        if (initialCapacity > MAXIMUM_CAPACITY)            initialCapacity = MAXIMUM_CAPACITY;        //首先根据我们指定的初始化容量和上面计算的桶的数量计算一个大概的hash表的长度        int c = initialCapacity / ssize;        //这里需要重新算一下,找到一个比initialCapacity大获相等的最小的一个ssize的倍数        if (c * ssize < initialCapacity)            ++c;        int cap = 1;        //上面我们找到的c只是一个辅助我们确定hash长度的数值,下面我们通过移位找到比c大的最小的2        的n次幂        while (cap < c)            cap <<= 1;        for (int i = 0; i < this.segments.length; ++i)            this.segments[i] = new Segment<K,V>(cap, loadFactor);    }    final Segment<K,V> segmentFor(int hash) {        return segments[(hash >>> segmentShift) & segmentMask];    }

上面有提到,为什么在计算桶的时候,要用hash >>> segmentShift 来定位,个人理解是,如果采用低位,很大程度上吧地位hash值相同或者类似的都会放到一个桶中,这样会造成entry数组中的某个元素的链表特别长,引起hash冲突。

下面参数使用的举个例子
例如,我们指定initialCapacity是31,loadFactor是0.75,concurrencyLevel 是17。我们看一下具体的桶大小和hash表的大小,以及扩容门槛的具体值。

桶的数量:大于concurrencyLevel的最小的2的n次幂,这里是,32。
hash表的长度:首先计算c,c=31/32=0,比31大且是32的倍数的,那么就是1。
扩容门槛:2*0.75 = 1.5,取整0。

这样我们放入一个元素后就会引起hash表的扩容,这里我们要注意,再次扩容时,桶的数量是固定的,扩大的是桶内的hash表。这里扩容时2倍。hashmap 2倍。hashtable 2倍+1。
下面验证一下我们的结论。通过加入断点,我们看一下。
这里写图片描述
没问题,桶的数量是32

这里写图片描述
hash表的长度也是1,加载因子0.75

按说put第一个元素是就要扩容,但是却没有。看一下具体的扩容函数,我们发现,判断是否要扩容的时候,主要是下面一段代码,其中count是当前hash表中的元素数量,初始为0。我们从上面得知threshold也为0,c++ > threshold 却没有成立,这里的情况和之前分析的i=i++有点类似,可以看这篇文章。其实正真的意思是,只有当前hash表中元素已经大于或等于threshold的了,在放入元素才会扩容在hash,如果当前数量为threshold-1,我们放入一个元素后数量为threshold,照样不会引起扩容。

 int c = count; if (c++ > threshold) // ensure capacity     rehash();
//默认初始容量static final int DEFAULT_INITIAL_CAPACITY = 16;//默认加载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的并发级别static final int DEFAULT_CONCURRENCY_LEVEL = 16;

默认初始化之后,桶的数量是16,桶内hash表的数量是1,加载因子是0。75。默认的参数扩容比较频繁,我们可以根据自己的要求初始化这三个参数。

0 0
原创粉丝点击