1.什么是ConcurrentHashMap
ConcurrentHashMap是java.util.concurrent包下AbstractMap的一个子类,此类遵守与Hashtable相同的功能规范,并且包括对应于Hashtable的每个方法的方法版本。
2.ConcurrentHashMap的设计思路
在JDK1.7中,ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
在JDK1.8中,取消了Segment分段锁的数据结构,取而代之的是数组+链表(红黑树)的结构。对每个数组元素加锁。
3.ConcurrentHashMap的方法
Get方法:
//JDK1.7 get方法
public V get(Object key) {
Segment s;
HashEntry[] tab;
int h = hash(key); //找出对应的segment的位置
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) { //使用Unsafe获取对应的Segmen
for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) { //找出对应的HashEntry,从头开始遍历
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
1.为输入的Key做Hash运算,得到hash值。
2.通过hash值,定位到对应的Segment对象。
3.再次通过hash值,定位到Segment当中数组的具体位置。
//JDK1.8 get方法
public V get(Object key) {
Node[] tab; Node e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
1.首先定位到table[]中的i。
2.若table[i]存在,则继续查找。
3.首先比较链表头部,如果是则返回。
4.然后如果为红黑树,查找树。
5.最后再循环链表查找。
Put方法:
//JDK1.7 put方法
//将一个HashEntry放入到该Segment中,使用自旋机制,减少了加锁的可能性
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry node = tryLock() ? null :
scanAndLockForPut(key, hash, value); //如果加锁失败,则调用该方法
V oldValue;
try {
HashEntry[] tab = table;
int index = (tab.length - 1) & hash; //同hashMap相同的哈希定位方式
HashEntry first = entryAt(tab, index);
for (HashEntry e = first;;) {
if (e != null) {
//若不为null,则持续查找,知道找到key和hash值相同的节点,将其value更新
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 { //若头结点为null
if (node != null) //在遍历key对应节点链时没有找到相应的节点
node.setNext(first);
//当前修改并不需要让其他线程知道,在锁退出时修改自然会
//更新到内存中,可提升性能
else
node = new HashEntry(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;
}
1.为输入的Key做Hash运算,得到hash值。
2.通过hash值,定位到对应的Segment对象。
3.获取可重入锁。
4.再次通过hash值,定位到Segment当中数组的具体位置。
5.插入或覆盖HashEntry对象。
6.释放锁。
//JDK1.8 put方法
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node[] tab = table;;) {
Node 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(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node 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 pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node p;
binCount = 2;
if ((p = ((TreeBin)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
1.参数校验。
2.若table[]未创建,则初始化。
3.当table[i]后面无节点时,直接创建Node(无锁操作)。
4.如果当前正在扩容,则帮助扩容并返回最新table[]。
5.然后在链表或者红黑树中追加节点。
6.最后还回去判断是否到达阀值,如到达变为红黑树结构。
Size方法:
//JDK1.7 size方法,求出所有的HashEntry的数目
public int size() {
final Segment[] 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 (;;) {
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 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。
2.把Segment的元素数量累加起来。
3.把Segment的修改次数累加起来。
4.判断所有Segment的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程中有修改,重新统计,尝试次数+1;如果不是。说明没有修改,统计结束。
5.如果尝试次数超过阈值,则对每一个Segment加锁,再重新统计。
6.再次判断所有Segment的总修改次数是否大于上一次的总修改次数。由于已经加锁,次数一定和上次相等。
7.释放锁,统计结束。
//JDK1.8 size方法
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
// 1.8加入的API
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
1.每个table[i]都有一个CounterCell与之对应。
2.把所有的table[i] 加在一起。
4.ConcurrentHashMap和HashMap以及HashTable的区别
HashMap: 线程不安全,在多线程情况下,HashMap在做put操作时,会在扩容时形成环状链表,这样在进行get操作时会引起死循环;HashMap的key值和value值都可以是null;
HashTable: HashTable和HashMap的实现原理几乎一样,HashTable是线程安全的,但是实现原理是在整个方法上加锁,这样导致性能非常差;HashTable不允许key和value为null;
ConcurrentHashMap:所采用的"分段锁"思想,不会存在锁竞争问题,可以提高效率
5.总结
ConcurrentHashMap是一种线程安全且高效的哈希表的解决方案,相比HashTable的全表锁在性能上的提升非常大。
6.引用
第一次写博客,找了很多好的博客,跟着博客去查找jdk源码,可以更快的理解其中的含义。
参考:
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
https://blog.csdn.net/fouy_yun/article/details/77816587
http://www.importnew.com/22007.html
http://www.sohu.com/a/205451532_684445
http://www.cnblogs.com/yydcdut/p/3959815.html