Java之ConcurrentHashMap线程安全原理
引言
对于Java程序员来说,HashMap是他们用到最多的映射(键值对)数据结构,但是HashMap是线程不安全的,在多线程的环境下,建议使用线程安全的ConcurrentHashMap。
锁机制
在JDK1.7及其之前,ConcurrentHashMap采用的是分段锁,也就是先将ConcurrentHashMap分成一定数量的段Segment,构成一个Segment数组,数组中的元素才是HashMap的键值对Node结点。可以理解为维护了一个Segment数组,数组中的元素是HashMap。
如此,在加锁时只需要对每个Segment段进行加锁即可,无需对整个ConcurrentHashMap加锁,提高了并发效率。
但是,对Segment段加锁,会导致统一段内的其它Node结点也无法被其它线程访问。
在JDK1.8及其之后,取消了分段机制,保留原有HashMap的数据结构,单独只对某一个Node结点进行加锁,不会影响其它结点,大大提高了锁粒度,提高了并发效率。
在JDK1.8及其之后,使用synchronized关键字以及CAS自旋锁保证多线程安全性。
当我们使用put方法新增键值对时:
public V put(K key, V value) {
return putVal(key, value, false);
}
会调用putVal方法:
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)
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)))
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 =