浅析ConcurrentHashMap线程安全
在JDK中,HashMap的线程安全版本有HashTable和ConcurrentHashMap。在HashTable类中是在方法上添加synchronized关键字保证的线程安全,同时只能有一个线程进行操作,所以HashTable属于同步容器,这样的效率其实是非常低的;
ConcurrentHashMap也是HashMap的线程安全版本,但是他允许多线程同时执行,所以属于并发容器。
那么作为并发容器的一种,他是怎么保证线程安全的呢?
一、前提
在JDK1.7版本中,主要采用分段锁(Segment类)进行,这样在不同数据操作的时候,可能使用了不同的锁,互不影响,尽可能多的保证了并发的性能;
本文基于JDK1.8版本,并未使用Segment,而是采用CAS+synchronized来保证线程的安全;
二、解析
主要从put方法来看ConcurrentHashMap是怎样处理的
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 在这里是不允许key、value为null值
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;
// 如果table还没有初始化,则进行初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 获取在table中当前hash位置的元素,如果当前hash位置没有值,则进行cas处理
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// cas比较并更新当前位置,如果是空则更新进去
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;
// 此处的f是当前hash位置的头节点,此处锁的是链表的头节点
synchronized (f) {
// 处理put逻辑,省略代码
if (tabAt(tab, i) == f) {
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
代码逻辑并不复杂,但是这种处理相较于HashTable的处理方式无疑好了很多
public synchronized V putIfAbsent(K key, V value) {}
HashTable是使用了synchronized锁了整个方法,效率很差,并不推荐使用;
三、总结
ConcurrentHashMap是在JUC包里的,其实在juc包里很多类(并发容器)都是基于cas、synchronized、ReentrantLock来实现,比如CopyOnWriteArrayList、ConcurrentSkipListMap等;
当然我们也可以自己使用ReentrantLock或者ReentrantReadWriteLock来实现一个线程安全的HashMap;
时在中春,阳和方起