ConcurrentHashMap源码分析(二)

上一篇分析了ConcurrentHashMap中的属性和构造器,这一篇记录下ConcurrentHashMap的put方法

方法

put 添加元素

    /**
     * 添加一个元素
     */
    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) {
        if (key == null || value == null) throw new NullPointerException();
        // 计算hash值
        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) {
                // 如果要插入的元素所在的桶还没有元素,就把这个元素插入到这个桶中
                // 桶就是这个数组下标位置的链表或树
                // 注意高效取模运算 hash&(n-1) == hash%n (n是2的m次幂)
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    // cas插入,如果成功插入了就break跳出循环,如果失败了,下次循环处理
                    // 成功代表cas插入时桶里仍然没有数据,也就是桶中第一元素是null
                    // 失败表示cas插入时桶里有数据了
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                // 如果要插入元素的桶的第一个元素的hash是MOVED,说明可能有扩容,当前线程帮忙一起迁移元素
                tab = helpTransfer(tab, f);
            else {
                // 如果当前桶中有元素,且不是在扩容,就先锁住这个桶(分段锁)
                // 然后查找当前要插入的元素在这个桶中是否已经存在了
                // 如果存在,就替换掉
                // 不存在,就插入到链表尾部,或树中
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            // 如果第一个元素的hash值大于等于0,说明没在扩容,也不是树,那么桶就是链表了
                            // 桶中的元素个数,记为1
                            binCount = 1;
                            // 死循环遍历桶,这里通过++binCount计算链表中的元素个数,这个值关系到链表转红黑树
                            // 这里可能有一个疑惑,如果桶没有遍历完,就找到了元素,那么++binCount得到的并不是桶中元素的总个数,是这样没错
                            // 但其实如果出现桶还没遍历完就找到了元素的情况,及当前元素还有next节点,说明在本次map的put之前,已经有和当前元素不同的元素放了进来,那么在那些元素put进来的时候同样进行了这个循环
                            // 如果那时++binCount已经达到了转树的条件,则本次就不会进这里(链表逻辑)了
                            // 如果那时++binCount还未达到转树条件,而本次循环在桶中找到了相同key的元素,则本次也不会满足转树的条件
                            // 那么逻辑就简单了,只有这里的循环到最后仍找不到元素,当前元素被插入到链表尾部,才有可能达到转树的条件
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                // 从桶中查找这个元素,如果找到了
                                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;
                                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;
                            // 记桶中元素个数为2
                            binCount = 2;
                            // 调用红黑树的插入方法
                            // 如果插入成功返回null,插入失败返回找到的节点
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                // 临时缓存旧的值
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    // 新值替换旧值
                                    p.val = value;
                            }
                        }
                    }
                }
                // 如果binCount不是0,说明成功插入元素或找到了元素
                if (binCount != 0) {
                    // 如果链表中的元素个数达到了8,将链表转为红黑树
                    // 如果链表已经树化了,但由于上面的树逻辑只为binCount赋值了2,没有计算树中的元素个数,达不到转树条件,所以不会重复树化
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        // 如果要插入的元素已经存在,就返回旧值
                        return oldVal;
                    // 跳出最外层循环
                    break;
                }
            }
        }
        // 成功插入了元素,元素个数加一
        addCount(1L, binCount);
        // 成功插入了元素返回null
        return null;
    }
    /**
     * 初始化table
     */
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
                // 如果sc < 0就yield,当前线程不进行初始化
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                // 这里compareAndSwap使用sc而不是sizeCtl的原因是sizeCtl被volatile修饰,
                // 如果有线程修改了sizeCtl的值,其他线程是可见的,所以先把sizeCtl赋值给局部变量sc,以此防止并发
                try {
                    if ((tab = table) == null || tab.length == 0) { 
                        // 数组的长度,如果没传,就是16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        // sc = n-n/4 = n*0.75
                        // 所以负载因子写死了0.75,传进来也没用
                        sc = n - (n >>> 2);
                    }
                } finally {
                    // 此时sizeCtl就是扩容阈值了
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

转载于:https://my.oschina.net/u/3295680/blog/3075491

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap是Java中的一个并发容器,用于在多线程环境中安全地存储和访问键值对。它使用了一些特殊的技术来提高其并发性能。 ConcurrentHashMap源码分析可以从几个关键点开始。首先,它使用了大量的CAS(Compare and Swap)操作来代替传统的重量级锁操作,从而提高了并发性能。只有在节点实际变动的过程中才会进行加锁操作,这样可以减少对整个容器的锁竞争。 其次,ConcurrentHashMap的数据结构是由多个Segment组成的,每个Segment又包含多个HashEntry。这样的设计使得在多线程环境下,不同的线程可以同时对不同的Segment进行操作,从而提高了并发性能。每个Segment都相当于一个独立的HashMap,有自己的锁来保证线程安全。 在JDK1.7版本中,ConcurrentHashMap采用了分段锁的设计,即每个Segment都有自己的锁。这样的设计可以在多线程环境下提供更好的并发性能,因为不同的线程可以同时对不同的Segment进行读写操作,从而减少了锁竞争。 总的来说,ConcurrentHashMap通过使用CAS操作、分段锁以及特定的数据结构来实现线程安全的并发访问。这使得它成为在多线程环境中高效地存储和访问键值对的选择。123 #### 引用[.reference_title] - *1* [ConcurrentHashMap 源码解析](https://blog.csdn.net/Vampirelzl/article/details/126548972)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *2* *3* [ConcurrentHashMap源码分析](https://blog.csdn.net/java123456111/article/details/124883950)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值