ConcurrentHashMap是一种线程安全的hashmap,相对于HashTable, 它拥有更高的并发性.
现在,我们就来分析一下在JDK1.8下的ConcurrentHashMap的实现及原理:
get()方法
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());//计算哈希值
if ((tab = table) != null && (n = tab.length) > 0 &&
//根据哈希值从table数组中获取到对应哈希桶中的头节点,未找到头结点返回null
(e = tabAt(tab, (n - 1) & h)) != null) {
//与头节点的哈希值进行比对,如果相等则返回头结点的val
if ((eh = e.hash) == h) {
//与头结点的key再进行比对
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//hash值为负值表示正在扩容,这个时候查的是ForwardingNode的find方法来定位到nextTable来
//eh=-1,说明该节点是一个ForwardingNode,正在迁移,此时调用ForwardingNode的find方法去nextTable里找。
//eh=-2,说明该节点是一个TreeBin,此时调用TreeBin的find方法遍历红黑树,由于红黑树有可能正在旋转变色,所以find里会有读写锁。
else if (eh < 0) {
return (p = e.find(h, key)) != null ? p.val : null;
}
//eh>=0,说明该节点下挂的是一个链表,直接遍历该链表即可。
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;//未找到对应的值
}
可以看到,在1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合比如hashtable、用Collections.synchronizedMap()包装的hashmap;安全效率高的原因之一。
ConcurrentHashMap的get操作为什么不需要加锁呢?
大家都知道,线程安全常常是因为获取的共享变量不一致而导致的,而get方法中访问到的共享变量只有Node类型的数组table,
transient volatile Node<K,V>[] table;
而volatile关键字可以保证共享变量的可见性, 即实时访问到其他线程修改后的数据. 那么,get方法不加锁的原因是因为table变量使用了volatile修饰么, 其实不然, volatile关键字对于基本类型的修改可以在随后对多个线程的读保持一致, 但是对于引用类型如数组,实体bean,仅仅保证引用的可见性,但并不保证引用内容的可见性。
真正的原因是因为在Node类中val变量和next变量是用volatile修饰了, 也是因为此处保证了ConcurrentHashMap的get方法的线程安全
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
由以上可以看出
get操作全程不需要加锁是因为Node的成员val是用volatile修饰的和数组用volatile修饰没有关系。
数组用volatile修饰主要是保证在数组扩容的时候保证可见性。
那么, Node数组table为什么要用volatile修饰呢?
其实就是为了使得Node数组在扩容的时候对其他线程具有可见性而加的volatile
put()方法:
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<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
//第一次添加元素时,初始化table数组
tab = initTable();
//添加到一个空的哈希桶时,通过CAS算法无锁插入数据
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//hash桶(tab[i])是fwd节点,表示正在扩容
else if ((fh = f.hash) == MOVED)
//扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//锁住哈希桶的头结点
synchronized (f) {
if (tabAt(tab, i) == f) {
//哈希值>=0说明是Node链表
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//新增元素的哈希值与头结点相同,则将值val替换为新值
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;
}
}
}
//红黑树
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) {
//链表长度>=8时,转换成红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//哈希桶存储的元素个数+1
addCount(1L, binCount);
return null;
}
从源码中看出,put方法在往空的哈希桶添加元素时,是采用CAS算法无锁插入的; 只有在发生哈希碰撞时,才会给哈希桶加上锁,锁是每个哈希桶的头结点.
remove()方法
public V remove(Object key) {
return replaceNode(key, null, null);
}
/**
* Implementation for the four public remove/replace methods:
* Replaces node value with v, conditional upon match of cv if
* non-null. If resulting value is null, delete.
* <p>
* 四个公共删除/替换方法的实现:用v替换节点值,条件是如果非空则匹配cv。如果结果值为null,请删除。
*
*/
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());//哈希值
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//table为空或者还未添加元素
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//正在扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
//锁住头结点
synchronized (f) {
if (tabAt(tab, i) == f) {
//链表结构
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
//红黑树
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
remove方法也是锁住哈希桶的头结点.依赖于CAS算法