参考小狼的简述blog http://www.jianshu.com/p/fb6e91b013cc
http://blog.csdn.net/qq924862077/article/details/74530103
参考mickole的blog http://www.cnblogs.com/mickole/articles/3757278.html
本文只用来作为个人总结
Java1.8的ConcurrentHashMap
一、使用CAS和Synchronized完成同步机制
1.Synchronized在java8中进行了很多优化性能提升很多
2.CAS:比较并替换 是一种硬件级别的操作。主要有三个概念:CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。在Java中CAS操作的核心类Unsafe。
ConcurrentHashMap中的三个CAS方法
//判断内存的offset位置是否存在一样的tab元素,从指定的内存位置获取对象保证table是最新的
@SuppressWarnings("unchecked")
static final Node tabAt(Node[] tab, int i) {
return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
判断obj对象的offset位置的值是否和expect相同,如果相同就用update替换
static final boolean casTabAt(Node[] tab, int i,
Node c, Node v) {
return U.compareAndSwapObject(object, offset, expect, update);
}
static final void setTabAt(Node[] tab, int i, Node v) {
U.putOrderedObject(tab, ((long)i << ASHIFT) + ABASE, v);
}
二、ConcurrentHashMap的重要概念
1.table:装载node节点的数组,采用懒加载的方式在第一次put数据的时候初始化,长度永远是2的幂数,默认是0
2.nextTable:在调整table大小的时候用到
3.sizeCtl:记录数组扩容和初始化的标识位,-1表示正在初始化,-(1+n)表示有n个活跃的线程正在修改table
4.ForwardingNode 一个hash值为-1( 0x8fffffff)的特殊节点
三、初始化table
1.hashTable容器的初始化是是在数组第一次put值的时候
2.initTable
当前ConcurrentHashMap的Table一定要是空的,这时候通过sizeCtl---记录table修改状态的标志位来判断是否可以初始化,如果sizeCtl<0 代表至少有一个线程在修改table。第一次修改时,会使用Usafe的compareAndSwapInt(对比覆盖的方法修改,防止其他线程同时修改过,这样保证只能有一个线程修改SIZECTl成功)将sizeCtl修改为-1。
private final Node[] initTable() {
Node[] tab; int sc;
//1.判断容器是否为空
while ((tab = table) == null || tab.length == 0) {
//sizeCtl是否小于0,小于0代表其他线程正在修改
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
//判断当前线程副本SIZECTL值是否和主存中一样,如果一样当前线程可以修改sizeCtl为-1,并返回true;
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node[] nt = (Node[])new Node,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
插入方法
插入方法实际上是由putVal来完成的,首先保证key和value不能为空。遍历整个容器,在容器指定位置插入值。
f:当前位置的node
n:容器的长度
i:要插入的位置
fh:当前位置元素的hash值
1.spread计算插入的位置
2.遍历容器
3.如果table为空需要初始化容器
4.使用tabAt方法判断table在(n-1)&hash这个位置是否插入过元素,如果没有f为空,那么在这个位置插入node元素,tabAt内部使用Usafe方法,保证table和主存的内容一致。
5.如果检测到有其他线程正在扩容,触发helpTransfer方法协助其他线程一同扩容
6.锁住当前的node,进行插入操作,这样只锁住一段代码相比于HashTable的方法锁性能上有很高提升。
7.如果发现一样的对象覆盖这个对象。如果没有在该位置插入一个新的node
8.如果是树,以树的方式插入。
9.addCount进行扩容
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;
}
get方法
它的get方法是没有用锁的,支持并发访问。通过CAS保证和主存中存在的table一致,再从这个table中遍历node查找到符合要求的数据
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;
}