深入剖析 Java 7 中的 HashMap 和 ConcurrentHashMap(2)

HashEntry 中,包含了 key 和 value 以及 next 指针(类似于 HashMap 中的 Entry),所以 HashEntry 可以构成一个链表。

2.1 成员变量及构造函数 public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable { ... //初始的容量 static final int DEFAULT_INITIAL_CAPACITY = 16; //初始的加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //初始的并发等级,表示当前更新线程的估计数 static final int DEFAULT_CONCURRENCY_LEVEL = 16; //最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; //最小的segment数量 static final int MIN_SEGMENT_TABLE_CAPACITY = 2; //最大的segment数量 static final int MAX_SEGMENTS = 1 << 16; // static final int RETRIES_BEFORE_LOCK = 2; // segments 的掩码值, key 的散列码的高位用来选择具体的 segment final int segmentMask; // 偏移量 final int segmentShift; final Segment<K,V>[] segments; ... // 创建一个带有指定初始容量、加载因子和并发级别的新的空映射 public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // 寻找最佳匹配参数(不小于给定参数的最接近的 2^n) int sshift = 0; // 用来记录向左按位移动的次数 int ssize = 1; // 用来记录Segment数组的大小 // 计算并行级别 ssize,因为要保持并行级别是 2^n while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } // 若为默认值,concurrencyLevel 为 16,sshift 为 4 // 那么计算出 segmentShift 为 28,segmentMask 为 15 this.segmentShift = 32 - sshift; this.segmentMask = ssize - 1; if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // 记录每个 Segment 上要放置多少个元素 int c = initialCapacity / ssize; // 假如有余数,则Segment数量加1 if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; // create segments and segments[0] Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }

当用 new ConcurrentHashMap() 无参构造函数进行初始化的,那么初始化完成后:

Segment 数组长度为 16,不可以扩容

Segment[i] 的默认大小为 2,负载因子是 0.75,得出初始阈值为 1.5,也就是以后插入第一个元素不会触发扩容,插入第二个会进行第一次扩容

这里初始化了 segment[0],其他位置还是 null,至于为什么要初始化 segment[0],后面的代码会介绍

当前 segmentShift 的值为 32 – 4 = 28,segmentMask 为 16 – 1 = 15,姑且把它们简单翻译为移位数和掩码,这两个值马上就会用到

2.2 put过程分析

根据 hash 值很快就能找到相应的 Segment,之后就是 Segment 内部的 put 操作。

public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); // 根据 hash 值找到 Segment 数组中的位置 j // hash 是 32 位,无符号右移 segmentShift(28) 位,剩下低 4 位, // 然后和 segmentMask(15) 做一次与操作,也就是说 j 是 hash 值的最后 4 位,也就是槽的数组下标 int j = (hash >>> segmentShift) & segmentMask; // 刚刚说了,初始化的时候初始化了 segment[0],但是其他位置还是 null, // ensureSegment(j) 对 segment[j] 进行初始化 if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); // 插入新值到 槽 s 中 return s.put(key, hash, value, false); }

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

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