ConcurrentHashMap的源码解读(JDK 1.7)
在java.util.concurrent包路径下
实现ConcurrentMap接口,该接口下的特有的方法:
V putIfAbsent(K key, V value)
如果指定键已经不再与某个值相关联,则将它与给定值关联。
boolean remove(Object key, Object value)
只有目前将键的条目映射到给定值时,才移除该键的条目。
V replace(K key, V value)
只有目前将键的条目映射到某一值时,才替换该键的条目。
boolean replace(K key, V oldValue, V newValue)
只有目前将键的条目映射到给定值时,才替换该键的条目。
构造函数:
主要构造函数:
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
int sshift = 0;
int ssize = 1; //大于concurrencyLevel最小的2的幂的值
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
创建的一个segment的内部hashentry数组
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];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
变量:
初始容量的默认值-》hashentry的table属性
int DEFAULT_INITIAL_CAPACITY = 16;
默认的加载因子 -》HashEntry
float DEFAULT_LOAD_FACTOR = 0.75f;
默认的并发度-》segment数组大小
int DEFAULT_CONCURRENCY_LEVEL = 16;
最大的容量 -》segment中数组的最大值 ->tab.length<MAXIMUM_CAPACITY
int MAXIMUM_CAPACITY = 1 << 30;
HashEntry数组的最小值
int MIN_SEGMENT_TABLE_CAPACITY = 2;
最大并发度=segment数组的最大值
int MAX_SEGMENTS = 1 << 16; // slightly conservative
锁重试次数
int RETRIES_BEFORE_LOCK = 2;
主要作用于keyhash过程
final int segmentMask;
final int segmentShift;
存储数据节点的位置
final Segment<K,V>[] segments;
static final class Segment<K,V> extends ReentrantLock implements Serializable {
//segment获取锁重试次数上限
static final int MAX_SCAN_RETRIES =Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
存放数据的
transient volatile HashEntry<K,V>[] table;
transient int count;
HashEntry的属性
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
}
底层的数据结构是:数组+数组+链表
集合:迭代器中以键形式遍历
transient Set keySet;
集合:迭代器中以键值对形式遍历时使用
transient Set<Map.Entry<K,V>> entrySet;
集合:迭代器以值得形式遍历时使用
transient Collection values;
put操作:
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
//concurrentHashMap中key和value不能为null
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
//找到key对应的segment数组的索引位置
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
//通过索引位置找到segment存储的segment对象
return s.put(key, hash, value, false);
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
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数组的索引位置
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;
//相等的情况下 onlyIfAbsent:false:当key相等时,返回旧value值,并将旧value替换新value值,true:仅将旧value值返回
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
//当前索引位置没有节点,或者是没有找到key相等的节点
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 void rehash(HashEntry<K,V> node) {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;
//按照2*table.length大小进行扩容
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++) {
//对旧table的所有的索引位置链表进行遍历
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
//头结点不会空,获取next的节点
int idx = e.hash & sizeMask;
//头结点重新哈希的位置
if (next == null) // Single node on list
//该索引位置只有一个节点,直接给新表的对应idx位置
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;
// Clone remaining nodes
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; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
}
获取元素:
get()
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
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;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
读操作对加锁的需求:
(1).HashEntry 中的 key,hash,next 都声明为 final 型:
在代码清单HashEntry 类的定义中我们可以看到,HashEntry 中的 key,hash,next 都声明为 final 型。
这意味着,不能把节点添加到链接的中间和尾部,也不能在链接的中间和尾部删除节点。
这个特性可以保证:在访问某个节点时,这个节点之后的链接不会被改变。
这个特性可以大大降低处理链表时的复杂性;
(2).HashEntry 类的 value 域被声明为 Volatile 型:
同时,HashEntry 类的 value 域被声明为 Volatile 型,
Java 的内存模型可以保证:某个写线程对 value 域的写入马上可以被后续的某个读线程“看”到。
在 ConcurrentHashMap 中,不允许用 null 作为键和值,当读线程读到某个 HashEntry 的 value 域的值为 null 时,
便知道产生了冲突(发生了重排序现象),需要加锁后重新读入这个 value 值。
这些特性互相配合,使得读线程即使在不加锁状态下,也能正确访问 ConcurrentHashMap;
由于对 Volatile 变量的写入操作将与随后对这个变量的读操作进行同步。
当一个写线程修改了某个 HashEntry 的 value 域后,另一个读线程读这个值域,
Java 内存模型能够保证读线程读取的一定是更新后的值。
所以,写线程对链表的非结构性修改能够被后续不加锁的读线程看到;
ConcurrentHashMap的高并发性主要来自于三个方面:
(1).用分离锁(Segment锁)实现多个线程间的更深层次的共享访问;
(2).用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求;
(3).通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性;