JDK7
JDK7中ConcurrentHash内部是由分段锁实现的(segment),每一个segment内部又是由hashEntry的数组组成,hash冲突的话是用链地址方法解决。
1.构造方法
@SuppressWarnings("unchecked")
public ConcurrentHashMap(int initialCapacity,//初始化大小
float loadFactor//扩容因子, int concurrencyLevel//并发度(默认16)) {
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;//表示当前当前segement=1<<sshift
int ssize = 1; //segement的zise
while (ssize < concurrencyLevel) {
//while循环 确保ssize的大小是大于切最接近concurrencyLevel的2的幂方数字
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift; //初始化为segmentShift =32-4 =28
this.segmentMask = ssize - 1; //15 ,这两个值后续用来做
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//cap为每个segment对应entry大小,默认为2.即一般情况下initialCapacity,
//若initialCapacity不为2的幂次方的话会进行设置为2的幂次方
//例如:initialCapacity = 33,ssize=16 c-> 3 ,cap->4,这个时候实际的大小则为4*16.。
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
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;
}
2.什么是segement?
static final class Segment<K,V> extends ReentrantLock implements Serializable
可以看出segment其实是一个reentrantLock,可重入锁。到这里就可以看出1.7的concurrentHash的并发是基于reentrantLock实现的。
3.put方法
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);//经过再hash得到的32位hashcode
int j = (hash >>> segmentShift) & segmentMask; //默认得到高4位&(2^4-1)确定segment的位置
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);
}
//定位segment的位置之后调用segment的put 方法放入元素
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
//调用当前segment的tryLock方法,tryLock内部是采用cas实现。若cas state(0,1)成功的话就为true,或者重进入成功的话state+1,若tryLock失败则一直循环tryLock,若是重复次数超过64次的话则直接调用lock方法,阻塞。等待加锁成功
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;//定位hash对应的table中处于的位置
HashEntry<K,V> first = entryAt(tab, index);//取出定位位置的第一个元素
for (HashEntry<K,V> e = first;;) {//循环遍历链表,然后将node设置在链表的尾部
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);//若是长度超过的阈值,进行rehash扩容
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();//释放锁。
}
return oldValue;
}
4.rehash方法
private void rehash(HashEntry<K,V> node) {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1; //长度*2
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++) {
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;//通过hash%长度定位位置
if (next == null) // Single node on list
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; //感觉这一步没啥用。找出一个通过hash定位不一致的。
// 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]);//将当前插入的这个node设置在链表的头部
newTable[nodeIndex] = node;
table = newTable;
}
5,get方法没什么好分析的,就是先hash高位定位segment,然后通过hash&size-1得到具体位置,遍历链表,得到具体的值
6.size()方法
public int size() {
try {
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
//可以看到size方法是自旋调用每一个segment 的lock方法。
//可以知道currentHashMap的size()方法是对于整体的性能是有影响的。
//在调用size方法会阻塞put方法等需要持有锁的方法
}
//省略。。。
}
JDK8
ConcurrentHashMap在1.8中去除了segment的实现方式,直接更改为node数组的方式
1,构造方法
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
// sizeCtl 的定义: When negative, the table is being initialized or resized:-1 for initialization,
else -(1 + the number of active resizing threads,
翻译过来就是,当值为负数的时候表示正在初始化或者在扩容,当值为-1的时候表示在初始化,
其他值得表示下一次扩容大小-1,
2.put方法:内部调用putval方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());//再hash后的值
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
//cas操作若是定位的索引上的值为null,
//则直接cas操作将当前key,value封装node设置
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);//若是hash值为-1的话表示有线程正在进行扩容操作,当前线程帮助数组扩容
else {
V oldVal = null;
//对对应索引位置上面的值进行加锁,成功之后再次验证链头元素是否为当前元素,若等于的话进行插入操作
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//上面的操作是若key的hash值相同且equals相同,则进行替换,二者有一不同,则添加到链表的尾部
else if (f instanceof TreeBin) {//若是树的形式的话进行红黑树的添加
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i); //若是大于了树化的阈值(8),则将链表转换成为树
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount); //binCount表示在索引位置保存了多少个元素,binCount+1
return null;
}