ConcurrentHashMap put get 源码解析

ConcurrentHashMap

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {}

可以看到,继承自AbstractMap, 实现了ConcurrentMap,以及可序列化接口。

先看put方法:

    /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <p>The value can be retrieved by calling the {@code get} method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}
     * @throws NullPointerException if the specified key or value is null
     */
    public V put(K key, V value) {
        return putVal(key, value, false);
    }

/** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //key value 不允许为null
        if (key == null || value == null) throw new NullPointerException();
        //计算key的hash值
        int hash = spread(key.hashCode());
        //这个数值不知道用来干啥的
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            //f是当前key值获取到的桶的首元素,n是数组的长度,即桶数量,i是数组索引,fh是f的hash值
            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) {
                //当table没有对应的索引桶,放入新数据的步骤,使用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)
                //Helps transfer if a resize is in progress. 不知道干啥,先放着
                tab = helpTransfer(tab, f);
            else {
                //数组非空,并且对应索引桶也非空,遍历桶,以添加新节点或者覆盖旧节点
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            //node是普通的节点
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                                (ek != null && key.equals(ek)))) {
                                    //hash值和key都相等,找到重复的键值
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    //遍历到达链表的末尾,仍然没找到重复的键值,添加新的node
                                    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) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

通过查看put方法,大概了解其步骤如下:

  当数组为空时,初始化数组结构,初始化步骤包含了自旋(通过判断数组是否为空,以及是否允许初始化的状态位)以及cas操作,以保证只有一个线程能够初始化成功,防止多次初始化。

  数组初始化成功后,计算key值的hash,通过hash对数组长度求余,获取数组索引值,通过索引获取数组的桶的首节点数据

  如果桶首元素为空,通过cas操作对空桶新增首元素。这里也是通过自旋,以及cas操作保证线程安全性,只有一个线程能够对空桶新增首元素。

  如果桶首元素不为空,并且数组正在transfer中(我的理解transfer是重构数组结构中,这个可以通过节点的hash值来判断,负数的hash值有特殊含义),则帮忙transfer(这个步骤,初步理解是这样的:transfer操作比较耗时,其他线程的帮忙,可以加快速度)

  如果桶首元素不为空,数组没有transfer,则锁定(synchronized)桶首元素,执行以下步骤

    如果桶数据结构为普通链表,遍历链表,根据遍历结果,新增节点或者覆盖旧节点

    如果桶数据结构为红黑树,遍历红黑树,根据遍历结果,新增节点或者覆盖旧节点

  最后判断新增节点后,是否需要将对应桶的链表(如果是链表的话)结构转换成红黑树,然后将旧元素的节点数据的value值返回(没有旧元素则返回null)

  

其中,计算key的hash值的方法如下:

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

而HashMap的计算Hash值的方法如下:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

区别如下:

  前者(ConcurrentHashMap)不需要考虑key为null的情况,因为不允许key和value为null。

  前者比后者计算多出来一步,即 “&HASH_BITS” 的操作 ,该操作的作用是把最高位(即符号位)设置为0。

对比jdk1.7和jdk1.8的实现,1.8弃用了1.7的Segment的概念,也就是分段锁。

1.7的分段锁Sement继承了Lock类,可以对每个Segment进行锁定,而其实每个Segment就是数组中的多个桶的集合,其锁定粒度,既不会太大,也不会太小,相对于HashTable的直接锁定整个数组而言,是一种优化。在1.8中被放弃的原因,个人理解如下:

  jdk1.8中,ConcurrentHashMap用synchronized取代Lock来保证线程安全,因为Synchronized是jdk内置的关键字,JVM可以对其进行优化,而Lock类对象则不行。这里涉及到锁升级,锁消除等。从轻量到重量,分为:偏向锁(无锁化)、轻量级锁(CAS,自旋),重量级锁(竞争的线程进入阻塞状态。并且操作系统在加锁和释放锁过程,需要在用户态和核心态之间进行切换,因为该操作依赖于操作系统的互斥锁,开销很大。当前线程释放锁后,需要从阻塞队列的对头拿到下一个线程,并进行唤醒操作)。

  1.8中,是对数组的每个桶进行加锁,相比Segment,锁粒度更小,并发度更高了。

get函数如下:

/**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code key.equals(k)},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @throws NullPointerException if the specified key is null
     */
    public V get(Object key) {
        
        //tab是数组,e是索引桶的首节点,p是,n是数组长度,eh是e的hash值,ek是e的key
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //求key的hash值
        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)
                //hash值小于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;
    }

源码对应java版本:

>java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)

猜你喜欢

转载自www.cnblogs.com/zhangxuezhi/p/11871927.html
今日推荐