收获:
1、缩小锁的力度,不同的段之间修改不影响
2、自旋锁,通过tryLock()尝试获取锁,拿不到的情况下,可以先去做别的事情
3、unsafe操作,直接通过内存地址操作数据,提高性能,提供了一些可见性的获取操作
4、跨段操作通过count计数来考虑别的段有没有被修改过
5、size\containValue等跨段操作不通过对每个段都加锁,而是先进行3次不加锁操作,
任意2次操作结果相同即为正确结果,若不同,再加锁操作
6、get\containKey等操作,由于key是final修饰的,不可被修改,可直接定位到Segment
再定位到桶,循环匹配
7、ConcurrentHashMap的结构:
ConcurrentHashMap->大于并发级别的最小2次幂个Segment数组->每个Segment包含
一个threshold*loadFactor大小的HashEntry数组->每个数据都是一个HashEntry上的节点
8、rehash()
HashMap实际上是一个线性数组,数组上的每个元素都是一个链表(俗称桶)。
ConcurrentHashMap则是对这个线下数组进行了划分,划分成若干个段(segment),在并发操作时,会对对应的段加锁。
HashEntry:用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的线性数组。每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
构造器
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//并发级别控制,默认16,最大1<<16
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
//ssize左移次数
int sshift = 0;
//segement大小,数值为大于并发级别的最小2次幂
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//segmentShift和segmentMask这两个变量是用来定位Segment的
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//cap的数值为 (cap × ssize > initialCapacity)满足这个情况下的最小2次幂
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
//创建segments数组并初始化第一个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.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
Segment
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
/**
* 在准备锁定段操作之前,可能阻塞获取之前在预扫描中tryLock的最大次数。
* 在多处理器上,使用有限数量的重试可维护在定位节点时获取的高速缓存。
*/
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
/**
* 每段维护的表,通过entryAt/setEntryAt访问元素,具有多线程可见性,即A线程修改了A的table,B线程是知晓的
*/
transient volatile HashEntry<K,V>[] table;
/**
* 元素的数量,没有被volatile修饰,如果是可见性的读取,依然可以多线程可见
*/
transient int count;
/**
* 元素变更的次数
*/
transient int modCount;
/**
* 当表的大小超过此阈值时,将进行rehash()
*/
transient int threshold;
/**
* 加载因子
*/
final float loadFactor;
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
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;
//找到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;
if (!onlyIfAbsent) {
e.value = value;
//该Segment修改计数器加一
++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;
//超过阈值,进行重hash
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
//该Segment修改计数器加一
++modCount;
//元素数量加一
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
/**
* rehash扩容
*/
@SuppressWarnings("unchecked")
private void rehash(HashEntry<K,V> node) {
//新建一个桶数组,大小是原来的2倍
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;
threshold = (int)(newCapacity * loadFactor);
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
//sizeMask用作定位索引
int sizeMask = newCapacity - 1;
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];
//如果桶不为null
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;
//如果桶只有一个元素,直接将新桶指向旧桶地址
if (next == null)
newTable[idx] = e;
else {
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
/**
* 从桶的第二个节点开始循环到最后一个节点,
* 如果最后一个节点的hash和桶的头结点不相等,
* 则将临时节点(lastRun)指向最后一个节点.
*/
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;
/**
* 从桶的第一个节点循环到倒数第二个节点,并添加到新桶中
* 每次从头部添加新元素
*/
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指向null
node.setNext(newTable[nodeIndex]);
//newTable[nodeIndex]指向node
newTable[nodeIndex] = node;
table = newTable;
}
/**
* 在等待Segment获取锁的时间内,做一些准备工作
*/
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
//获取该hash下的桶(即HashEntry[])的第一个HashEntry
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
//重试次数
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f;
if (retries < 0) {
//如果想要put的这个key在当前Segment中不存在,新建一个HashEntry
if (e == null) {
if (node == null)
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
//如果想要put的这个key在当前Segment中存在,且位于桶的第一个节点
else if (key.equals(e.key))
retries = 0;
/**
*如果想要put的这个key在当前Segment中存在,但不在桶的第一个节点,
*将e指向桶的下一个节点,即该位置就是需要插入的位置
*/
else
e = e.next;
}
//如果超过重试次数,直接拿锁,拿不到就阻塞
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
/**
* 如果retries=0且该hash位置的桶的第一个HashEntry已经被修改,
* 则将修改过后的值赋给e,重新循环
* 这里解释一下,retries=0表示每次新循环的第二次循环,
* 这里可以校验每次新循环的第一次循环做出的决定。
* 很好理解,因为我的桶变了,所以我要重新执行这段代码!
*/
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f;
retries = -1;
}
}
return node;
}
/**
* 用来获取锁
*/
private void scanAndLock(Object key, int hash) {
// similar to but simpler than scanAndLockForPut
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f;
if (retries < 0) {
if (e == null || key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f;
retries = -1;
}
}
}
/**
* 本质就是链表的删除,只是加了一些额外的操作而已
*/
final V remove(Object key, int hash, Object value) {
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
//找到需要被删除的链表(桶),需要删除的是这个链表上的某个节点
HashEntry<K,V> e = entryAt(tab, index);
HashEntry<K,V> pred = null;
while (e != null) {
K k;
HashEntry<K,V> next = e.next;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
if (value == null || value == v || value.equals(v)) {
if (pred == null)
//如果是头结点,通过相对于tab的内存偏移量覆盖
setEntryAt(tab, index, next);
else
//如果不是头结点,通过相对于当前HashEntry的内存偏移量覆盖
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
}
//将临时节点指向下一个节点
pred = e;
e = next;
}
} finally {
unlock();
}
return oldValue;
}
final boolean replace(K key, int hash, V oldValue, V newValue) {
if (!tryLock())
scanAndLock(key, hash);
boolean replaced = false;
try {
HashEntry<K,V> e;
for (e = entryForHash(this, hash); e != null; e = e.next) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
if (oldValue.equals(e.value)) {
e.value = newValue;
++modCount;
replaced = true;
}
break;
}
}
} finally {
unlock();
}
return replaced;
}
final V replace(K key, int hash, V value) {
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V> e;
for (e = entryForHash(this, hash); e != null; e = e.next) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
e.value = value;
++modCount;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
final void clear() {
lock();
try {
HashEntry<K,V>[] tab = table;
for (int i = 0; i < tab.length ; i++)
setEntryAt(tab, i, null);
++modCount;
count = 0;
} finally {
unlock();
}
}
}
HashEntry
HashEntry用来封装具体的键值对,是个典型的四元组。volatile保证了ConcurrentHashmap读操作并不需要加锁。
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);
}
}
}
Put
1.定位segment并确保定位的Segment已初始化
public V put(K key, V value) {
Segment<K,V> s;
//concurrentHashMap不允许key/value为空
if (value == null)
throw new NullPointerException();
//hash函数对key的hashCode重新散列,避免差劲的不合理的hashcode,保证散列均匀
int hash = hash(key);
//返回的hash值无符号右移segmentShift位与段掩码进行位运算,定位segment
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject
(segments, (j << SSHIFT) + SBASE)) == null)
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
segmentShift
和segmentMask
这两个全局变量的主要作用是用来定位Segment。
int j =(hash >>> segmentShift) & segmentMask
segmentMask
:段掩码,假如segments数组长度为16,则段掩码为16-1=15;segments长度为32,段掩码为32-1=31。这样得到的所有bit位都为1,可以更好地保证散列的均匀性。
segmentMask = ssize - 1
segmentShift
:2的sshift次方等于ssize,segmentShift=32-sshift。若segments长度为16,segmentShift=32-4=28;若segments长度为32,segmentShift=32-5=27。
而计算得出的hash值最大为32位,无符号右移segmentShift,则意味着只保留高几位(其余位是没用的),然后与段掩码segmentMask位运算来定位Segment。
2 << sshift = ssize
segmentShift = ssize - sshift
Get
public V get(Object key) {
Segment<K,V> s;
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;
/**
* value的修改,我们保证是线程安全的。key被final修饰且e.key,e.value
* 都是原子性的操作。
*/
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
containsKey
@SuppressWarnings("unchecked")
public boolean containsKey(Object key) {
Segment<K,V> s; // same as get() except no need for volatile value read
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 true;
}
}
return false;
}
Size
public int size() {
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow;
long sum;
long last = 0L;
int retries = -1;
try {
for (;;) {
/**
*先尝试3次不对segment加锁,获取count,如果这3次中,有相邻2次获取的count是一样的,
*那么说明在获取size的过程中,没有其他线程进行插入或删除,即该size是准确的。
*否则,直接加锁,对每个segment进行count的计算。
*/
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;
}
containsValue
看到containValue,是不是疑惑了:为啥不能直接通过循环获取Segment,再获取HashEntry,然后遍历整个桶,查看value是否变化。虽然HashEntry的value是volatile修饰的,但value的修改不是原子的操作。
public boolean containsValue(Object value) {
// Same idea as size()
if (value == null)
throw new NullPointerException();
final Segment<K,V>[] segments = this.segments;
boolean found = false;
long last = 0;
int retries = -1;
try {
outer: for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
long hashSum = 0L;
int sum = 0;
for (int j = 0; j < segments.length; ++j) {
HashEntry<K,V>[] tab;
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null && (tab = seg.table) != null) {
for (int i = 0 ; i < tab.length; i++) {
HashEntry<K,V> e;
for (e = entryAt(tab, i); e != null; e = e.next) {
V v = e.value;
if (v != null && value.equals(v)) {
found = true;
break outer;
}
}
}
sum += seg.modCount;
}
}
if (retries > 0 && sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return found;
}