多线程高并发编程(10) -- ConcurrentHashMap源码分析

一.背景

  前文讲了HashMap的源码分析,从中可以看到下面的问题:

  • HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低下;
  • 在遍历迭代获取时进行修改(put/remove)操作,会导致发生并发修改异常(ConcurrentModificationException);
  • 在JDK1.7之前,对HashMap进行put添加操作,会导致链表反转,造成链表回路,从而发生get死循环,(当然这个问题在JDK1.8被改进了按照原链表顺序进行重排移动);
  • 如果多个线程同时检测到元素个数超过 数组大小 * loadFactor,这样就会发生多个线程同时对数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失;

  基于上述问题,都可以使用ConcurrentHashMap进行解决,ConcurrentHashMap使用分段锁技术解决了并发访问效率,在遍历迭代获取时进行修改操作也不会发生并发修改异常等等问题。

  二.源码解析

  1. 构造方法:

    //最大容量大小
        private static final int MAXIMUM_CAPACITY = 1 << 30;
        //默认容量大小
        private static final int DEFAULT_CAPACITY = 16;
        /**
         *控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
         *  多线程之间,以volatile方式读取sizeCtl属性,来判断ConcurrentHashMap当前所处的状态。
         *  通过cas设置sizeCtl属性,告知其他线程ConcurrentHashMap的状态变更
         *未初始化:
         *  sizeCtl=0:表示没有指定初始容量。
         *  sizeCtl>0:表示初始容量。
         *初始化中:
         *  sizeCtl=-1,标记作用,告知其他线程,正在初始化
         *正常状态:
         *  sizeCtl=0.75n ,扩容阈值
         *扩容中:
         *  sizeCtl < 0 : 表示有其他线程正在执行扩容
         *  sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 :表示此时只有一个线程在执行扩容
         */
        private transient volatile int sizeCtl;
        //并发级别
        private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
        //创建一个新的空map,默认大小是16
        public ConcurrentHashMap() {
        }
        public ConcurrentHashMap(int initialCapacity) {
            if (initialCapacity < 0)
                throw new IllegalArgumentException();
            //调整table的大小,tableSizeFor的实现查看前文HashMap源码分析的构造方法模块
            int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
            this.sizeCtl = cap;
        }
        public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
            this.sizeCtl = DEFAULT_CAPACITY;
            putAll(m);
        }
        public ConcurrentHashMap(int initialCapacity, float loadFactor) {
            this(initialCapacity, loadFactor, 1);
        }
        /**
         * concurrencyLevel:并发度,预估同时操作数据的线程数量
         * 表示能够同时更新ConccurentHashMap且不产生锁竞争的最大线程数。
         * 默认值为16,(即允许16个线程并发可能不会产生竞争)。
         */
        public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel) {
            if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
                throw new IllegalArgumentException();
            //至少使用同样多的桶容纳同样多的更新线程来操作元素
            if (initialCapacity < concurrencyLevel)   // Use at least as many bins
                initialCapacity = concurrencyLevel;   // as estimated threads
            long size = (long)(1.0 + (long)initialCapacity / loadFactor);
            int cap = (size >= (long)MAXIMUM_CAPACITY) ?
                MAXIMUM_CAPACITY : tableSizeFor((int)size);
            this.sizeCtl = cap;
        }
    复制代码

  2. put:

    public V put(K key, V value) {
            return putVal(key, value, false);
        }
        static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash普通节点哈希的可用位
        //把位数控制在int最大整数之内,h ^ (h >>> 16)的含义查看前文的put源码解析
        static final int spread(int h) {
            return (h ^ (h >>> 16)) & HASH_BITS;
        }
        final V putVal(K key, V value, boolean onlyIfAbsent) {
            //key和value为空抛出异常
            if (key == null || value == null) throw new NullPointerException();
            //得到hash值
            int hash = spread(key.hashCode());
            int binCount = 0;
            //自旋对table进行遍历
            for (Node<K,V>[] tab = table;;) {
                Node<K,V> f; int n, i, fh;
                //初始化table
                if (tab == null || (n = tab.length) == 0)
                    tab = initTable();
                //如果hash计算出的槽位元素为null,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为-1,说明正在扩容,那么就帮助其扩容。以加快速度
                else if ((fh = f.hash) == MOVED)
                    tab = helpTransfer(tab, f);
                else {
                    V oldVal = null;
                    synchronized (f) {// 同步 f 节点,防止增加链表的时候导致链表成环
                        if (tabAt(tab, i) == f) {// 如果对应的下标位置的节点没有改变
                            if (fh >= 0) {//f节点的hash值大于0
                                binCount = 1;//链表初始长度
                                // 死循环,直到将值添加到链表尾部,并计算链表的长度
                                for (Node<K,V> e = f;; ++binCount) {
                                    K ek;
                                    //hash和key相同,值进行覆盖
                                    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;
                                    //hash和key不同,添加到链表后面
                                    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;
                    }
                }
            }
            // 判断是否需要扩容
            addCount(1L, binCount);
            return null;
        }
    复制代码

    1. initTable:初始化

      private final Node<K,
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值