浅析结构
在jdk1.7中ConcurrentHashMap整体上可以看作是一个双层数组的结构。外层是Segment数组,数组的每个Segment在操作时都相对独立,在加锁的过程中只会对某个Segment加锁而不是整个数组。每个Segment内是一个HashEntry数组,类似于一个HashMap。
//无参构造器会用默认参数调用该构造器
//参数为默认的初始容量,加载因子,并发级别 initialCapacity==16,loadFactor==0.75f,concurrencyLevel==16
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//检查各参数是否符合要求
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//检查并发级别(即Segment数组的大小)是否超过最大值
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
//扩容移位条件sshift
int sshift = 0;
//用于重新计算Segment数组的大小
int ssize = 1;
//将ssize调整为最小的2的n次幂且大于等于concurrencyLevel
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//根据sshift与ssize计算将来用于定位到相应Segment的参数segmentShift与segmentMask
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
//计算每个Segment内的数组HashEntry的大小
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//c用来计算Segment内数组的大小 此处为向上取整操作
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
//cap为真正的Segment内的数组大小 需要调整为2的n次幂
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;// 创建容量
//创建0位置的Segment
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
//创建Segment数组
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
//将s0赋值给Segment[0]
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
重要属性
//默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
//加载因子 用来确定数组的扩容阈值
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认并发级别 即Segmengts数组的大小
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//每个Segment内HashEntry数组的最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//每个segment内HashEntry数组的最小长度
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
//Segment数组的最大长度
static final int MAX_SEGMENTS = 1 << 16;
重要方法
put
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
//将hash的高位移到低位 segmentMask在构造器中被初始为Segment数组长度减1
//此处的与运算操作相当于对Segment数组的大小取余 求出hash值对应位置
int j = (hash >>> segmentShift) & segmentMask;
//判断要插入的Segment位置是否为空
if ((s = (Segment<K,V>)UNSAFE.getObject
(segments, (j << SSHIFT) + SBASE)) == null)
//创建Segment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE;
Segment<K,V> seg;
//防止多线程并发操作已经对当前位置创建了Segment
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
//从Segment[0]获取参数 HashEntry数组的大小和加载因子
Segment<K,V> proto = ss[0];
int cap = proto.table.length;
float lf = proto.loadFactor;
//计算扩容阈值
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
//再次确认是否有其它线程已经创建了Segment
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
//创建Segment
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
//再次确认是否有其它线程已经创建了Segment
//使用while的原因是若在CAS操作时失败,即已经有其它线程创建了Segment,可以在循环体判断中获取到被其它线程创建的Segment
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
//CAS为原子操作即两个线程执行一定会有先后顺序 若u位置为null将seg赋给ss的u位置
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//tryLock 尝试获取锁,即使获取失败,后面的代码也会继续执行
//Lock 获取锁,若获取失败,则进入阻塞状态
//此时若尝试获取锁失败,则会引入自旋锁
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
//下面为链表的插入操作
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
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 {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
//获取要插入的Segment的第一个节点
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
//while循环为一直尝试获取锁 使用tryLock可以在未获取锁的时候进行一些其它操作提高效率
//retries变量起到引导作用 判断当前应该进行什么操作
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f;
//若retries小于0 则进行遍历链表操作
//若遍历到表尾则创建HashEntry或者找到key相同的HashEntry 将retries置为0
if (retries < 0) {
if (e == null) {
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
else if (key.equals(e.key))
retries = 0;
else
e = e.next;
}
//此时为遍历链表的操作已经结束还未获取到锁 不能一直处于循环状态占用CPU内存
//每进行一次循环会让retries变量+1 当retries大于64时 调用Lock方法
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
//判断在循环等待的过程中 链表结构是否发生了变化即头结点发生改变 若改变则将retries置为-1 重新进行遍历链表操作
//为了避免在判断头结点是否改变的过程中 释放的锁被其它线程抢走 只在retries为奇数时判断
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}