嘿嘿,我就知道面试官接下来要问我 ConcurrentHashMap 底层原理了,看我怎么秀他 (3)

PS: 文章注释中 (1)(2)(3) 等序号都是用来方便做标记,不是计算值

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { //检验参数是否合法。值得说的是,并发级别一定要大于0,否则就没办法实现分段锁了。 if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); //并发级别不能超过最大值 if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments //偏移量,是为了对hash值做位移操作,计算元素所在的Segment下标,put方法详讲 int sshift = 0; //用于设定最终Segment数组的长度,必须是2的n次幂 int ssize = 1; //这里就是计算 sshift 和 ssize 值的过程 (1) while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift; //Segment的掩码 this.segmentMask = ssize - 1; if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //c用于辅助计算cap的值 (2) int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; // cap 用于确定某个Segment的容量,即Segment中HashEntry数组的长度 int cap = MIN_SEGMENT_TABLE_CAPACITY; //(3) while (cap < c) cap <<= 1; // create segments and segments[0] //这里用 loadFactor做为加载因子,cap乘以加载因子作为扩容阈值,创建长度为cap的HashEntry数组, //三个参数,创建一个Segment对象,保存到S0对象中。后边在 ensureSegment 方法会用到S0作为原型对象去创建对应的Segment。 Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); //创建出长度为 ssize 的一个 Segment数组 Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; //把S0存到Segment数组中去。在这里,我们就可以发现,此时只是创建了一个Segment数组, //但是并没有把数组中的每个Segment对象创建出来,仅仅创建了一个Segment用来作为原型对象。 UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }

上边的注释中留了 (1)(2)(3) 三个地方还没有细说。我们现在假设一组数据,把涉及到的几个变量计算出来,就能明白这些参数的含义了。

//假设调用了默认构造,都用的是默认参数,即 initialCapacity 和 concurrencyLevel 都是16 //(1) sshift 和 ssize 值的计算过程为,每次循环,都会把 sshift 自增1,并且 ssize 左移一位,即乘以2, //直到 ssize 的值大于等于 concurrencyLevel 的值 16。 sshfit=0,1,2,3,4 ssize=1,2,4,8,16 //可以看到,初始他们的值分别是0和1,最终结果是4和16 //sshfit是为了辅助计算segmentShift值,ssize是为了确定Segment数组长度。 //(2) 此时,计算c的值, c = 16/16 = 1; //判断 c * 16 < 16 是否为真,真的话 c 自增1,此处为false,因此 c的值为1不变。 //(3) 此时,由于c为1, cap为2 ,因此判断 cap < c 为false,最终cap为2。 //总结一下,以上三个步骤,最终都是为了确定以下几个关键参数的值, //确定 segmentShift ,这个用于后边计算hash值的偏移量,此处即为 32-4=28, //确定 ssize,必须是一个大于等于 concurrencyLevel 的一个2的n次幂值 //确定 cap,必须是一个大于等于2的一个2的n次幂值 //感兴趣的小伙伴,还可以用另外几组参数来计算上边的参数值,可以加深理解参数的含义。 //例如initialCapacity和concurrencyLevel分别传入10和5,或者传入33和16 put()方法

put 方法的总体流程是,

通过哈希算法计算出当前 key 的 hash 值

通过这个 hash 值找到它所对应的 Segment 数组的下标

再通过 hash 值计算出它在对应 Segment 的 HashEntry数组 的下标

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxgxf.html