一、ConcurrentHashMap的数据结构
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable {
final int segmentMask; //用于确定key所在的segment,key的hash值被用来选择segment
final int segmentShift;
final Segment<K,V>[] segments; //一个HashMap中包含多个segments,每个segments都是一个HashTable
}
其中segmentMask、和segmentShift用于定位key所在的Segment。
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
transient volatile int count; //当前Segment中元素的数量
transient int modCount; //??
transient int threshold; //当count的值超过threshold之后就rehash
transient volatile HashEntry<K,V>[] table; //
final float loadFactor; //负载因子
}
从上面中我们可以看到Segment继承了ReentranLock,也就是说我们每一段都加了一个锁,因此如果有一个线程需要向Map中添加Element的时候,其他线程就不能访问该Segment了,这样就改善了HashTable锁住整张表的性能,同时保证了线程安全。
二、Hash方法和定位段
第一步、给定一个key,计算器hash值的方法。
private static int hash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
第二步、segmentFor方法用于确定key映射到那个Segment中
final Segment<K,V> segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}
第三步、然后根据每个Segment中的table.length确定key所在的table的index
int index = hash&(table.length-1);
所以确定一个key的位置需要经历以上三个步骤
三、put方法
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock(); //1、对所有访问这个段的put方法的线程加锁
try {
int c = count;
if (c++ > threshold) //2、如果Segment中的HashEntry的数量超过了threshold,那么执行rehash操作
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1); //3、定位key在table中的位置
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue;
if (e != null) {//4、如果e!=null表示table[index]中已经存在与之相等的key,替换value
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else { //5、否则将该key-value作为头结点添加到table[index]中
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock(); //释放锁
}
}
执行过程
步骤 | 描述 |
---|---|
步骤一 | 对所有访问这个段的put方法的线程加锁 |
步骤二 | 如果Segment中的HashEntry的数量超过了threshold,那么执行rehash操作 |
步骤三 | 定位key在table中的位置 |
步骤四 | 如果e!=null表示table[index]中已经存在与之相等的key,替换value |
步骤五 | 否则将该key-value作为头结点添加到table[index]中 |
步骤六 | 释放锁 |
get方法
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
HashEntry<K,V> getFirst(int hash){
return tab[hash & (tab.length-1)];
}
V readValueUnderLock(HashEntry<K,V> e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
对于get方法,如果一个线程执行get方法,二另一个线程执行put方法,如果put的key-value是Map中已存在的key,那么put方法替换后的value值肯定能够被get方法看到(根据volatile读写之间的happens before关系可以知道)。但是如果线程执行的put方法put一个key-value是当前Map中不存在的,那么get方法可能获取不到新添加的key-value。因此HashMap是一弱一致性的。详细情况请见http://ifeve.com/concurrenthashmap-weakly-consistent/
参考文章
http://www.iteye.com/topic/344876
http://ifeve.com/concurrenthashmap-weakly-consistent/
https://my.oschina.net/hosee/blog/639352#h2_9