ConcurrentHashMap源码解读
数据结构
源码中的声明
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable {
//底层就是一个Segment数组
final Segment<K,V>[] segments;
//初始化时候Segment数组的默认长度
static final int DEFAULT_INITIAL_CAPACITY = 16;
//加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认的线程并发级别,就是并发数,默认16个线程进行并发
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//最大容量entryies的个数
static final int MAXIMUM_CAPACITY = 1 << 30;
//Segment数组的最小容量
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
//最大的Segament的个数
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
ConcurrentHashMap就是一个Segment数组,每个Segment里面存储的是一个哈希表。
static final class Segment<K,V> extends ReentrantLock implements Serializable {
// Hash table,默认初始容量为2,特别注意,这里table用了volatile修饰,
// 保证多线程读写时的可见性
transient volatile HashEntry<K,V>[] table;
// segment中hashtable的元素数量计数器,用于size方法中,分段计算汇总
transient int count;
// 执行更新操作时,获取segment锁的重试次数,多核CPU重试64次,单核CPU重试1次
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
// 其他省略
}
Segment继承ReentrantLock,每个Segment元素都一个锁,实现了分段锁,锁的粒度更细化,支持更高的并发。
ConcurrentHashMap初始化
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;//用来定义Segment[]的长度
//ssize的长度是大于并发级别数的最小2次方
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//定义每个Segment元素里面哈希表的长度
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;//默认哈希表的长度是2
//cap是每个Segment元素哈希表的长度,最后也是2的n次方
while (cap < c)
cap <<= 1;
//创建Segment[0]和Segment数组
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为sun.misc.Unsafe对象,使用CAS操作,
// 将segments[0]的元素替换为已经初始化的s0,保证原子性。
// Unsafe类采用C++语言实现,底层实现CPU的CAS指令操作,保证原子性。
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
从构造方法看出,根据并发数计算Segment数组的长度ssize和每个HashEntry数组的长度cap,并初始化Segment[0].
ssize的长度是2的n次方,并且默认长度为16,每个hashEntry数组的长度也是2的n次方,最小为2
put方法实现
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
private int hash(Object k) {
int h = hashSeed;
if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// 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);
}
1. 根据key计算出hash值,由此得到Segment元素的下标位置
2. 检查下标segment是否已经初始化,如果没有初始化,则调用ensureSegment进行初始化,内部用了CAS操作进行替换,达到初始化效果。初始化的过程进行了双重检查,UNSAFE.getObjectVolatile,通过这个方法执行了两次,以检查segment是否已经初始化,以及用UNSAFE.compareAndSwapObject进行CAS替换,CAS的替换有失败的可能,因此源码中还加了自旋重试的操作,保证最终CAS操作的成功。
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
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<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
3. 调用Segment的put方法,将元素放到HashEntry数组中,过程中会去获取锁
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<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;
}
当有线程A和线程B在相同segment对象上put对象时,执行过程如下:
1、线程A执行tryLock()方法获取锁。
2、线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,
在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B;
这样设计目的是为了让线程切换和自旋消耗的CPU的时间达到平衡,不至于白白浪费CPU,也不会过于平凡切换线程导致更多的CPU浪费。
3、获得锁之后,根据hash值定位到HashEntry数组的下标,更新或插入元素,在插入过程中,如果HashEntry数组元素个数容量超过负载比例,
则进行rehash操作扩容,扩容为原来的两倍(rehash请对比Java集合-HashMap源码实现深入解析中的逻辑,自行分析,基本一模一样);
4、在插入后,还会更新segment的count计数器,用于size方法中计算map元素个数时不用对每个segment内部HashEntry遍历重新计算,提高性能。
5、当线程A执行完插入操作后,会通过unlock()方法释放锁,接着唤醒线程B继续执行;
get方法
get方法是不回去获取锁的,根据key计算hash值,定位到Segment元素位置,并且使用UNSAFE的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;
}
size方法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 {
for (;;) {
//尝试RETRIES_BEFORE_LOCK次后还无法统计到正确的大小,就将
//整个Segment数组锁住,进行hashEntry数组的长度累计
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
//两次查询modCount,判断数据结构是否没有变化,如果没有变化,则是跳出循环,返回size
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;
}
1. 重复计算Segment的modCount 和 hashEntry数组的size大小,并汇总
2. 前一次的sum和后一次的last(分别表示两次的modCount汇总)相等,表示数据结构没有变化,就返回累计的size
3. 否则,就再次检查sum和last,尝试RETRIES_BEFORE_LOCK次还是无法统计到正确的值,就将整个Segment数组都锁住,累计size大小,并在finally中释放锁
注意,累计的size大小是大概的值,比如说如果在last==sum情况下,跳出循环,再返回size之前,存在一个线程put元素,返回的值就会有问题
转载:https://www.jianshu.com/p/47c1be88a88e上一篇: Linux安装JDK1.8
下一篇: Linux 安装 JDK1.8
推荐阅读
-
jdk源码——集合(ConcurrentHashMap)
-
ConcurrentHashMap源码整理
-
ConcurrentHashMap源码分析
-
ConcurrentHashMap源码解读
-
ConcurrentHashMap源码深入解析
-
beanstalkd协议解读(中文翻译加个人理解) 博客分类: 分布式队列 beanstalkd
-
beanstalkd协议解读(中文翻译加个人理解) 博客分类: 分布式队列 beanstalkd
-
elasticsearch2.0源码在开发环境eclipse中启动的问题及解决方案 博客分类: 开源软件 elasticsearch
-
elasticsearch2.0源码在开发环境eclipse中启动的问题及解决方案 博客分类: 开源软件 elasticsearch
-
一个关闭显示器的程序源码 XPWindows