JAVA并发容器源码分析【二】ConcurrentHashMap分析之putVal

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

文章首发于:clawhub.club


基于JDK8的ConcurrentHashMap中的putVal源码及注释:

     /** Implementation for put and putIfAbsent
         * 实现put和putIfAbsent
         * onlyIfAbsent含义:如果我们传入的key已存在我们是否去替换,true:不替换,false:替换。
         * */
        final V putVal(K key, V value, boolean onlyIfAbsent) {
            //键值都不为空
            if (key == null || value == null) throw new NullPointerException();
            //发散键的hash值
            int hash = spread(key.hashCode());
            //桶数量 0
            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) {
                    //通过CAS方法添加键值对
                    if (casTabAt(tab, i, null,
                                 new Node<K,V>(hash, key, value, null)))
                        break;                   // no lock when adding to empty bin 添加到空桶时没有锁
                }
                //如果当前节点的Hash值为MOVED
                else if ((fh = f.hash) == MOVED)
                    //如果还在进行扩容操作就先进行扩容
                    tab = helpTransfer(tab, f);
                else {
                    V oldVal = null;
                    //synchronized锁定此f节点
                    synchronized (f) {
                        //再次检测节点是否相同
                        if (tabAt(tab, i) == f) {
                            //如果此节点hash值大于0
                            if (fh >= 0) {
                                //桶数量为1
                                binCount = 1;
                                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;
                                        //onlyIfAbsent:如果我们传入的key已存在我们是否去替换,true:不替换,false:替换。
                                        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;
                                //找到插入的位置,插入节点,平衡插入
                                if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                               value)) != null) {
                                    oldVal = p.val;
                                    if (!onlyIfAbsent)
                                        p.val = value;
                                }
                            }
                        }
                    }
                    //如果桶数量不为0
                    if (binCount != 0) {
                        //如果桶数量大于树化阈值
                        if (binCount >= TREEIFY_THRESHOLD)
                            //将链表转为树
                            treeifyBin(tab, i);
                        //如果老的值存在,则返回
                        if (oldVal != null)
                            return oldVal;
                        break;
                    }
                }
            }
            //增加数量
            addCount(1L, binCount);
            return null;
        }

这个方法的大概思路就是:

  1. 初始化操作
  2. 如果待插入键的插入位置没有节点,则通过CAS方式创建一个节点。
  3. 如果当前节点的Hash值为 static final int MOVED = -1; // hash for forwarding nodes 转发节点的哈希,则调用helpTransfer方法,如果还在进行扩容操作就先进行扩容,Helps transfer if a resize is in progress。
  4. 再有就是发生Hash碰撞时通过synchronized关键字锁定当前节点,这里有两种情况,一种是链表形式:直接遍历到尾端插入,一种是红黑树:按照红黑树结构插入。
  5. 计数

这里面有两个地方保证了putVal的并发实现,一个是没有待插入节点时的CAS技术,一个是发现有存在Hash碰撞时的synchronized关键字。

这里面涉及到“初始化”、“扩容”、“计数”的相关操作后期分析。


来源:https://www.jianshu.com/p/a75d196731c0

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » JAVA并发容器源码分析【二】ConcurrentHashMap分析之putVal

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏