浅析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;

posted @ 2021-02-06 17:18  faylinn  阅读(306)  评论(0编辑  收藏  举报
、、、