jdk7 ConcurrentHaspMap源码解析
最近在学习HashMap的源码,jdk7和jdk8的ConcurrentHashMap源码有很大的不同,以下的是jdk7版本的源码解析。如有错漏,请不吝指正。
ConcurrentHashMap的存储结构
//ConcurrentHashmap使用segment 数组分段,segment数组的每一个元素都有一个HashEntry数组属性存储数据
ConcurrentHashmap
segment[] table;
segment
HashEntry[] tab;
以下是ConcurrentHashMap的两个内部类Segment和HashEntry,解析重心放在了put方法,没有对这两个类做很深的解析
HashEntry
/**
* ConcurrentHashMap list entry. Note that this is never exported
* out as a user-visible Map.Entry.
* ConcurrentHaspMap的内部类
*/
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/**
* Sets next field with volatile write semantics. (See above
* about use of putOrderedObject.)
*/
final void setNext(HashEntry<K,V> n) {
UNSAFE.putOrderedObject(this, nextOffset, n);
}
// Unsafe mechanics
static final sun.misc.Unsafe UNSAFE;
static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class k = HashEntry.class;
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
Segment
/**
* Segments are specialized versions of hash tables. This
* subclasses from ReentrantLock opportunistically, just to
* simplify some locking and avoid separate construction.
*段是哈希表的专门版本。这个来自ReentrantLock的子类是有机会的,只是为了简化一些锁定并避免单独的构造。
*方法太多,不一一列举
* Segment继承了ReentrantLock
*/
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
/**
* The maximum number of times to tryLock in a prescan before
* possibly blocking on acquire in preparation for a locked
* segment operation. On multiprocessors, using a bounded
* number of retries maintains cache acquired while locating
* nodes.
* 最大尝试扫描次数,该属性会在segment.scanAndLockForPut()中使用
*/
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
/**
* The per-segment table. Elements are accessed via
* entryAt/setEntryAt providing volatile semantics.
* 用来存储数据的对象数组
*/
transient volatile HashEntry<K,V>[] table;
/**
* The number of elements. Accessed only either within locks
* or among other volatile reads that maintain visibility.
* 元素的数量。只能在锁中或在其他维护可见性的volatile读之间进行访问。
*/
transient int count;
/**
* The total number of mutative operations in this segment.
* Even though this may overflows 32 bits, it provides
* sufficient accuracy for stability checks in CHM isEmpty()
* and size() methods. Accessed only either within locks or
* among other volatile reads that maintain visibility.
* 修改次数
*/
transient int modCount;
/**
* The table is rehashed when its size exceeds this threshold.
* (The value of this field is always <tt>(int)(capacity *
* loadFactor)</tt>.)
* 当表的大小超过此阈值时,表将被重新映射。 (此字段的值始终为<tt>(int)(capacity * loadFactor)</ tt>。)
*/
transient int threshold;
/**
* The load factor for the hash table. Even though this value
* is same for all segments, it is replicated to avoid needing
* links to outer object.
* @serial
* 加载因子
*/
final float loadFactor;
}
Segment()
//在新建Segment对象的时候会把加载因子,segement的阈值,和hashEntry数组初始化
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
属性
/* ---------------- Constants -------------- */
/**
* The default initial capacity for this table,
* used when not otherwise specified in a constructor.
* 此表的默认初始容量,在构造函数中未指定时使用。
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The default load factor for ths table, used when not
* otherwise specified in a constructor.
* 此表的默认加载因子,在构造函数中未另行指定时使用。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The default concurrency level for this table, used when not
* otherwise specified in a constructor.
* 此表的默认并发级别,在构造函数中未指定时使用。
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* The maximum capacity, used if a higher value is implicitly
* specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
* 最大容量,如果两个带参数的构造函数隐式指定了较大的值,则使用。必须是2的幂且小于等于 1<<30,以确保项可以使用int进行索引。
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The minimum capacity for per-segment tables. Must be a power
* of two, at least two to avoid immediate resizing on next use
* after lazy construction.
* 每个段表的最小容量。必须是2的幂,至少是2,以避免在惰性构造后的下次使用时立即调整大小。
*/
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
/**
* The maximum number of segments to allow; used to bound
* constructor arguments. Must be power of two less than 1 << 24.
* 允许的最大段数;用于绑定构造函数参数。必须是2的幂且小于1 << 24。
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
* Number of unsynchronized retries in size and containsValue
* methods before resorting to locking. This is used to avoid
* unbounded retries if tables undergo continuous modification
* which would make it impossible to obtain an accurate result.
* 在使用锁定之前在大小和containsValue方法上的非同步重试次数。这是用来避免在表经过连续修改而无法获得准确结果时进行无限制的重试。
*/
static final int RETRIES_BEFORE_LOCK = 2;
/**
* Mask value for indexing into segments. The upper bits of a
* key's hash code are used to choose the segment.
* 分段索引的掩码值。键的哈希码的上位用于选择段。
*/
final int segmentMask;
/**
* Shift value for indexing within segments.
* 用于段内索引的位移值。
*/
final int segmentShift;
/**
* The segments, each of which is a specialized hash table.
* 这些段,每个段都是一个专用的哈希表。
*/
final Segment<K,V>[] segments;
transient Set<K> keySet;
transient Set<Map.Entry<K,V>> entrySet;
transient Collection<V> values;
构造函数
ConcurrentHashMap()
/**
* Creates a new, empty map with a default initial capacity (16),
* load factor (0.75) and concurrencyLevel (16).
* DEFAULT_INITIAL_CAPACITY:默认初始容量,所有segment数组的容量之后
* DEFAULT_LOAD_FACTOR:默认加载因子
* DEFAULT_CONCURRENCY_LEVEL:默认并发级别,ConcurrentHashMap中segment的个数
*/
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
@SuppressWarnings("unchecked")
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;
// Find power-of-two sizes best matching arguments
//找到大于等于并发级别的最小2的幂
int sshift = 0;
int ssize = 1;//代表所有segment的总大小
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//用来计算segement所在的下标,通过(hash >>> segmentShift) & segmentMask的方式
//segmentShift:hashcode右移的数值
//segmentMask:分段索引的掩码值,低位全为1,与hash值右移segmentShift位后的值相与,最后获取到对应segement所在的下标
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
//判断初始容量是否大于最大容量,大于则让初始容量等于最大容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//使用ConcurrentHashMap的初始容量除以所有Segment总大小,计算出每个segment想要的大小
int c = initialCapacity / ssize;
//判断想要的segment总容量是否小于ConcurrentHashMap的初始容量
//如果是,说明segment总容量不能满足ConcurrentHashMap的初始容量,因此给segment的容量+1,直达满足为止
if (c * ssize < initialCapacity)
++c;
//MIN_SEGMENT_TABLE_CAPACITY=2:segment的最小大小为2
int cap = MIN_SEGMENT_TABLE_CAPACITY;
//判断要设置的Segment数组的大小是否小于想要的大小
//小于则Segment数组的大小增大一倍
//否则使用该cap大小设置Segment数组的大小
while (cap < c)
cap <<= 1;
//以上代码的意义在于确保Segment数组和HashEntry数组的大小都是2的幂
// 创建 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;
}
UNSAFE的使用
UNSAFE里有很多保证内存的方法,在ConcurrentHashMap中,使用到了不少UNSAFE方法,以下代码调用UNSAFE方法使用CAS操作保证了线程安全。
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Person {
private int i = 0;
private static sun.misc.Unsafe UNSAFE;
private static long I_OFFSET;
static {
try {
//普通的类使用的是应用类加载器,不能获取UnSafe对象,只能通过反射的方式获取,一般不建议用户使用
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
I_OFFSET = UNSAFE.objectFieldOffset(Person.class.getDeclaredField( "i"));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final Person person = new Person();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
// person.i++;
if(UNSAFE.compareAndSwapInt(person,I_OFFSET,person.i, person.i+1)){
System.out.println(UNSAFE.getIntVolatile(person,I_OFFSET));
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if(UNSAFE.compareAndSwapInt(person,I_OFFSET,person.i, person.i+1)){
System.out.println(UNSAFE.getIntVolatile(person,I_OFFSET));
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
put
put()
/**将指定的键映射到此表中的指定值。
*键和值都不能为null。
*
* <p>可以通过使用等于原始键的键调用<tt> get </ tt>方法来检索该值。
*
* @param key与指定值关联的键
* @param与指定键关联的值
* @返回与<tt> key </ tt>关联的先前值,如果没有<tt> key </ tt>的映射,则返回<tt> null </ tt>
* @throws NullPointerException如果指定的键或值是null
*/
@SuppressWarnings("unchecked")
public V put(K key, V value) {
Segment<K,V> s;
//value为null直接抛空指针异常
if (value == null)
throw new NullPointerException();
//计算hash值
int hash = hash(key);
//j表示segment数组的下标的位置,
//segmentMask:segment数组大小减一
//(hash >>> segmentShift):取高位去算下标,
//取高位计算segment下标,可能是为了与取低位计算hashEntry下标区分开来,使要添加到hashEntry的元素能够添加到各个位置上,例如当segment数组和hashEntry数组都是16位的时候,如果两者都使用低位计算下标,即j=hash&segmentMask,这时由于hash和segmentMask都相同,计算出的下标都相同,插入的元素的位置就永远是在第j个segment下的hashEntry数组里的第j个位置
int j = (hash >>> segmentShift) & segmentMask;//使用hash的高位计算下标
//UNSAFE.getObject(segments, (j << SSHIFT) + SBASE):获取segments第j个位置的值
//判断segments第j个位置的值为不为空
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//为空则在segments第j个位置生成一个segment对象
s = ensureSegment(j);
//生成完后,再调用segment对象的put方法,把数据存到segment里
return s.put(key, hash, value, false);
}
ensureSegment(int k)
/**返回给定索引的段,创建它并记录在段表中(通过CAS)(如果尚不存在)。
* @param k索引
* @返回段
*/
@SuppressWarnings("unchecked")
private Segment<K,V> ensureSegment(int k) {
//先获取一下Segment数组
final Segment<K,V>[] ss = this.segments;
//计算出偏移量
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
//先获取Segment数组对应位置的segment,有可能会是多线程执行以下代码
//如果获取为null,则新建一个segment
//如果获取到一个segment对象,说明其它线程已经新建了segment对象,本线程不需要再生成一个新的对象
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
//使用segment数组里的第0个segment作为原型,新建segment
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;//获取原型的长度
float lf = proto.loadFactor;//获取原型的加载因子
int threshold = (int)(cap * lf);//计算阈值
//新建一个HashEntry数组,让新建的segement的属性segments指向该数组
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
//重新获取并检查segment数组对应下标下的segment是否为空
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
//还是为空,才生成segment对象
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
//让seg不断尝试获取segment数组第u个位置的值
//获取到非空值就不进行循环了,直接返回seg
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
//使用cas操作把ss的u位置上的空值赋值成新生成的那个segment对象,操作成功则返回,
//不成功则继续尝试获取该位置的值,并判断该值是否为null
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
segment.put()
//这是segment里的put方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//首先,先尝试加一次锁,加锁成功,node=null
//如果加锁失败,调用scanAndLockForPut()不断尝试加锁,在尝试加锁的过程中,会试图生成一个要添加的HashEntry对象
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;//先获取segment对象内部的table
int index = (tab.length - 1) & hash;//使用hash的低位计算下标
//获取tab第index位的entry元素,由于获取的entry元素可能是链表的头结点,所以用first表示
HashEntry<K,V> first = entryAt(tab, index);
//遍历链表
for (HashEntry<K,V> e = first;;) {
//判断正在遍历的结点是否为空
if (e != null) {
K k;
//如果找到key和hash都相等的结点且,onlyIfAbsent为false的情况下就对该结点的value进行修改
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;//赋新值
++modCount;//修改次数加一
}
break;//找到直接退出循环
}
e = e.next;//找不到,继续找下一个结点
}
else {
//如果在加锁的过程中,已经预建立一个结点,将该结点的next指向对应的头结点
if (node != null)
node.setNext(first);
else
//新建一个hashEntry对象,其next属性指向first,因此采用的是头插法
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;//该segment对象里的元素数量加一
//如果加一后大于阈值并且tab的长度小于最大容量
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
//扩容,将表的大小加倍并重新打包条目,同时将给定结点添加到新表中
rehash(node);
else
//不扩容,直接把新结点放到hashEntry对应位置下
setEntryAt(tab, index, node);
++modCount;//修改次数加一
count = c;//更新该segment对象里的元素数量
oldValue = null;//因为是插入,没有旧值,相当于旧值为null
break;
}
}
} finally {
unlock();//解锁
}
return oldValue;//put方法返回的是久值
}
segment.scanAndLockForPut()
/**
*扫描包含给定key的节点,当试图获取锁时,如果没有找到包含给定key的节点,会创建并返回一个。在返回时,保证锁被持有。与大多数方法不同,对method equals的调用没有经过筛选:由于遍历速度无关紧要,我们还可以帮助预热相关的代码和访问。
* @返回一个新节点,如果键没有找到,否则为空
*/
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
//获取给segment和hash的entry
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node 尝试标记
//不断尝试加锁,加锁失败进入循环
//使用while(!tryLock())可以在尝试加锁失败的时候,做其它的事情
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {//加锁失败后如果retries < 0,会遍历对应位置下的链表
//如果链表没有数据或遍历完链表最后一个结点后
if (e == null) {
//如果没有创建过结点,就大胆地去创建一个key-value对应的结点,此时结点的next为null
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
//遍历完后retries置零
retries = 0;
}
//如果在遍历过程中,找到与key对应的结点,则不用新建结点,直接让retries置零
else if (key.equals(e.key))
retries = 0;
//如果没有遍历完或没有遍历到对应的结点,就准备遍历下一个结点,在遍历下一个结点之前会尝试获取一次锁,获取成功就不遍历了
else
e = e.next;
}
//如果超过允许的最大扫描次数,直接进行加锁操作,加锁成功才返回
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
//retries & 1:隔一次重试,偶数才会重试
//重新检查头结点是否发生改变(可能因为其它线程添加了元素等操作导致头结点改变)
//如果头结点不同了,把新的头结点赋值给first,retries变回-1,继续遍历链表..
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
segment.entryForHash()
/**
* Gets the table entry for the given segment and hash
* 获取给定段和散列的表条目
*/
@SuppressWarnings("unchecked")
static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) {
HashEntry<K,V>[] tab;
return (seg == null || (tab = seg.table) == null) ? null :
(HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
}
segment.entryAt()
/**
* Gets the ith element of given table (if nonnull) with volatile
* read semantics. Note: This is manually integrated into a few
* performance-sensitive methods to reduce call overhead.
* 获取具有可变读取语义的给定表的第i个元素(如果为非null)。
* 注意:这是手动集成到一些对性能敏感的方法中的,以减少调用开销。
*/
@SuppressWarnings("unchecked")
static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) {
return (tab == null) ? null :
(HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)i << TSHIFT) + TBASE);
}
segment.rehash()
/**
* Doubles size of table and repacks entries, also adding the
* given node to new table
* 将表的大小加倍并重新打包条目,同时将给定结点添加到新表中
*/
@SuppressWarnings("unchecked")
private void rehash(HashEntry<K,V> node) {
/**有道翻译如下:
将每个列表中的节点重新分类为新表。因为我们使用的是2的幂展开,所以每个容器中的元素必须保持在相同的索引中,或者以2的幂偏移量移动。我们通过捕捉由于下一个字段不更改而可以重用旧节点的情况来消除不必要的节点创建。从统计学上讲,在默认阈值下,当表重复使用时,只有大约六分之一需要克隆。它们替换的节点一旦不再被并发遍历表中的任何读线程引用,就将成为垃圾回收。入口访问使用纯数组索引,因为后面跟着易失性表写。
*/
HashEntry<K,V>[] oldTable = table;//HashEntry旧表,每个segment都有一个HashEntry数组
int oldCapacity = oldTable.length;//旧表容量
int newCapacity = oldCapacity << 1;//新表容量为旧表容量2倍
threshold = (int)(newCapacity * loadFactor);//新表阈值
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];//新表
int sizeMask = newCapacity - 1;//新表掩码,用来计算下标
//遍历旧表
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];//获取旧表遍历到的元素
if (e != null) {//如果元素不为空
HashEntry<K,V> next = e.next;//获取下一个结点
int idx = e.hash & sizeMask;//计算该结点在新表的下标
if (next == null) //如果是单个结点的链表
newTable[idx] = e;//直接把该结点放到新表的对应位置下
else { // Reuse consecutive sequence at same slot
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
//遍历旧表下的链表,找到该链表中最后那段在新表中下标相同的链表串
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;//计算该结点在新表的下标
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
//把最后一段链表串的头结点放到新结点的对应位置
newTable[lastIdx] = lastRun;
//克隆剩下的结点
//遍历一个,克隆一个
//把克隆的结点的next属性指向新表对应位置下的元素(没有则next为null)
//最后新表的对应位置下的元素更新为新克隆的结点
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
int nodeIndex = node.hash & sizeMask;//计算新结点在新表的下标
node.setNext(newTable[nodeIndex]);//把新结点的下一个结点设置成新表里对应位置下的元素
newTable[nodeIndex] = node; // 添加新的结点到新表
table = newTable;//让该segment的hashEntry数组指向新表
}
node.setNext()
/**
* Sets next field with volatile write semantics. (See above
* about use of putOrderedObject.)
* 使用volatile写语义设置下一个字段。(见上面关于putOrderedObject的使用。)
*/
final void setNext(HashEntry<K,V> n) {
UNSAFE.putOrderedObject(this, nextOffset, n);
}
segment.setEntryAt()
/**
* Sets the ith element of given table, with volatile write
* semantics. (See above about use of putOrderedObject.)
*/
static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
HashEntry<K,V> e) {
UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}
/**
* The maximum number of times to tryLock in a prescan before
* possibly blocking on acquire in preparation for a locked
* segment operation. On multiprocessors, using a bounded
* number of retries maintains cache acquired while locating
* nodes.
* 在可能阻塞获取以准备锁定段操作之前,尝试在预扫描中尝试锁定的最大次数。
* 在多处理器上,使用有限数量的重试可以维护在定位节点时获取的缓存。
*/
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
//获取运行计器的CPU核数,如果核数大于1则最大次数取64
get
get()
/**返回指定键所映射的值,
*或{@code null}(如果此映射不包含键的映射)。
*
* <p>更正式的说法是,如果此映射包含键的映射
*将{@code k}设置为值{@code v},以使{@code key.equals(k)},
*然后此方法返回{@code v}; 否则返回
* {@code null}。 (最多可以有一个这样的映射。)
*
* @throws NullPointerException如果指定的键为null
*/
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key);//通过key计算hash
//使用hash高位与segmentMask掩码相与计算出下标的偏移量
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
//获取segments数组u位置下的segment,并判断是否为null
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
//使用hash低位计算下标,获取对应位置的hashEntry对象
//遍历该对象对应的链表
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
//找到key和hash都相同的hashEntry对象,并返回该对象的value
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
remove
remove()
size
/**返回此映射中的键值映射数。
*如果映射包含多个<tt> Integer.MAX_VALUE </ tt>元素,
*则返回<tt> Integer.MAX_VALUE </ tt>。
*
* @返回此映射中的键值映射数
*/
public int size() {
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry
try {
//先遍历每个segment,统计所有segment里的元素数量之和,作为size,如果在此过程中,该concurrentHashMap对象没有被修改过,则返回size(),否则继续遍历;
//直到retries++ == RETRIES_BEFORE_LOCK时,会对所有segment进行加锁,再统计元素数量
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;//统计修改次数
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;//判断是否溢出
}