我是靠谱客的博主 俏皮小笼包,最近开发中收集的这篇文章主要介绍ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

ConcurrentHashMap_get与remove方法源码分析

文章目录

  • ConcurrentHashMap_get与remove方法源码分析
    • 1. get() 方法
    • 1.1 ForwardingNode的find() 方法
    • 2. remove() 方法

1. get() 方法

与普通的Map一样,通过key查找元素。

在ConcurrentHashMap的get方法中也没有任何加锁逻辑。

/**
 * 返回指定键映射到的值,如果该映射不包含键的映射,则返回null。
 * 更正式地说,如果这个映射包含从键k到值v的映射,使得key.equals(k),
 * 那么这个方法返回v;否则将返回null。(最多只能有一个这样的映射。)
 * @param key 要返回其关联值的键
 * @return
 */
public V get(Object key) {
    // tab 引用table数组
    // e 当前元素
    // p 目标元素
    // eh 当前元素hash
    // ek 当前元素key
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 获得扰动运算后的hash值
    int h = spread(key.hashCode());

    // 条件1:table不为 null
    // 条件2:长度大于0
    // 条件3:桶位上的元素不为 null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        /*
            对比与头结点的key是否完全一致
             1.比较key的hash值是否一致
             2.比较key的内存地址是否一致 或 进行equals的返回true
             同时满足这两个条件才能认为key完全一致
         */
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                // 直接在桶位上找到了对应元素,直接返回value值
                return e.val;
        }
        /*
            hash值小于0,有两种情况
            1. hash = -1,表示当前桶位是FWD节点,表示当前桶位中的节点已经被迁移了
            2. hash = -2, 表示当前桶位是TreeBin节点,
            如果是 FWD 节点的话,会进入对应的 FWD 中定义的 find 方法进行查找
            如果是红黑树,则会进入TreeNode内部的find方法进行查找(fwd节点和treeBin都继承了Node节点并重写了find方法)
         */
        else if (eh < 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;
}

流程总结:

  1. 计算key的hash值
  2. 根据key的hash计算桶位位置,判断桶位上是否有元素
  3. 当前桶位如果是fwd节点或者是红黑树节点,则调用各自的find()方法。
  4. 当前桶位不是fwd节点也不是红黑树节点,则遍历桶位,进行查找。

get方法中调用了FWD和TreeBin中的find方法。

下面对find方法进行分析。

1.1 ForwardingNode的find() 方法

同样没有加锁逻辑。

Node<K,V> find(int h, Object k) {
        // 循环以避免转发节点上的任意深度递归
        // tab: 新建散列表的引用
        outer: for (Node<K,V>[] tab = nextTable;;) {
        //------------------外轮自旋开始--------------------
            // 新散列表上元素的引用
            // 新散列表长度
            Node<K,V> e; int n;
            // 条件1:永远不成立
            // 条件2:永远不成立
            // 条件3:永远不成立
            // 条件4:在新扩容表中重新路由定位 hash 对应的头结点
            //        true ->  1.在 oldTable 中对应的桶位在迁移之前就是 null
            //        false -> 2.扩容完成后,有其它写线程,将此桶位设置为 null
            if (k == null || tab == null || (n = tab.length) == 0 ||
                (e = tabAt(tab, (n - 1) & h)) == null)
                return null;

            //------------------内轮自旋开始--------------------
            // 前置条件:扩容后的表对应hash的桶位一定不是null,e为此桶位的头结点
            // e可能表示的类型:
            //    1.node 类型
            //    2.TreeBin 类型
            //    3.FWD 类型
            for (;;) {
                // eh 新扩容后表指定桶位的当前节点的hash
                // ek 新扩容后表指定桶位的当前节点的key
                int eh; K ek;
                // 如果成立:表示出入的key命中新扩容的表中的数据
                if ((eh = e.hash) == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    // 返回命中元素
                    return e;

                // eh < 0 有两种情况
                //      1. 表示TreeBin类型
                //      2. FWD类型(新扩容的表,在并发很大的情况下再次发生了扩容,所以可能在此方法再次拿到FWD类型)
                if (eh < 0) {
                    // 判断是否是FWD结点
                    if (e instanceof ForwardingNode) {
                        tab = ((ForwardingNode<K,V>)e).nextTable;
                        // 调到外轮自旋
                        continue outer;
                    }
                    else
                        // 说明此桶位为 TreeBin 节点,使用TreeBin.find 查找红黑树中相应节点。
                        return e.find(h, k);
                }
                // 前置条件:当前桶位头结点 并没有命中查询,说明此桶位是链表
                // 1. 将当前元素指向链表的下一个元素
                // 2. 判断当前元素的下一个位置是否为空
                //     true -> 说明迭代到链表末尾,未找到对应的数据,返回Null
                if ((e = e.next) == null)
                    return null;
            }
            //------------------内轮自旋结束--------------------
        }
        //------------------外轮自旋结束--------------------
    }
}

2. remove() 方法

 public V remove(Object key) {
        //调用了替换节点的方法
        return replaceNode(key, null, null);
    }
  		 ||
		 ||
         /    
   /*
    *  @param Object key -> 表示要替换的key
    *  @param V value -> 要替换的目标值
    *  @param Object cv(compare value) -> 如果cv不为NULL,则需要既对比key还要对比		
    *  cv,这两个参数都一致,才能替换目标值
    */
 final V replaceNode(Object key, V value, Object cv) {
      
        //计算key的hash  
        int hash = spread(key.hashCode());
        
        //引用哈希表
        Node<K,V>[] tab = table;
     
        //自旋
        for (;;) {
            
            /*
             * f表示桶位头节点
             * n 表示table长度
             * i表示hash寻址后命中的下标
             * fh表示桶位头节点的hash值
             */
            Node<K,V> f; int n, i, fh;
            
            /*
             *  CASE1
             *  如果table为NULL,或者当前寻址后命中的桶位没有元素,
             *  直接break循环。
             */
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break; 
            
            /*
             *  CASE2
             *  当前桶位是fwd节点,说明当前table正在扩容中,
             *  由于当前是写操作,需要尝试帮助table扩容。
             */
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            
            /*   
             *   CASE3
             *   当前桶位是链表或者红黑树
             */
            else {
                //保留替换之前的value
                V oldVal = null;
                
                //校验标记 用于判断是否锁错对象,从而进入下一次的自旋。
                boolean validated = false;
				
                //锁定当前桶位的头结点(分段锁思想)
                synchronized (f) {
                    
                    /*
                     * 判断锁定的对象是否是当前桶位头节点,
                     * 防止当前线程加锁之前,其他线程修改过桶位的头节点。
                     */
                    if (tabAt(tab, i) == f) {
                        
                        //哈希值大于0,说明桶位为正常的Node节点(或者Node链表)
                        if (fh >= 0) {
                            //validated赋值为true。
                            validated = true;
                          	
                            /*
                             *  e 表示当前循环的元素
                             *  pred 表示上一个元素 
                             */
                            Node<K,V> e = f, pred = null;
                            
                            for (;;) {
                                //当前桶位的key。
                                K ek;
                                
                                //找到了指定key
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    
                                    //将找到的指定节点的val赋值给ev
                                    V ev = e.val;
                                    
                                    /*
                                     *  cv == null -> true 表示这是一个删除操作
                                     *  
                                     *  cv == ev -> true 表示这是一个替换操作
                                     */
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        oldVal = ev;
                                        //直接将value替换
                                        if (value != null)
                                            e.val = value;
                                        
                                        /*
                                         * 当前节点不是头结点,并且这是一次删除操作
                                         * 直接在链表中将e删除即可
                                         */
                                        else if (pred != null)
                                            pred.next = e.next;
                                        
                                        /*
                                         * e是头结点,调用setTab方法将e.next设置为										  						        	
                                         * 头结点即可,变向的将e删除。
                                         */
                                        else
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                pred = e;
                                //没有找到指定节点,退出。
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        //红黑树
                        else if (f instanceof TreeBin) {
                            //修改标志位
                            validated = true;
                            
                            //转换为指定的TreeBin节点
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            
                            /* 
                             *  r 表示红黑树根节点
                             *  p 表示红黑树中查找到的对应的key一致的Node。
                             */
                            TreeNode<K,V> r, p;
                            if ((r = t.root) != null &&
                                //在红黑树中找到了指定的节点
                              (p = r.findTreeNode(hash, key, null)) != null) {
                              
                                V pv = p.val;
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    
                                    //原值赋给oldVal。
                                    oldVal = pv;
                                    
                                    //value不为NULL,替换
                                    if (value != null)
                                        p.val = value;
                                    
                                    //删除
                                    else if (t.removeTreeNode(p))
                                        
                                     /* 
                                      * 如果删除后树的元素个数较少则退化成链表
                                      * t.removeTreeNode(p)这个方法返回true表示删
                                      * 除节点后树的元素个数是否较少(boolean) 
                                      */
                                     setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                
                /*
                 * 当其他线程修改过桶位的头节点时,说明当前线程锁错了对象
                 * 此时validated为false,不会进入到下面的if的逻辑中终止,则会进入下次自旋
                 * 
                 * 如果此时validated为true,说明进入过真正的删除逻辑中,然后判断是否真正
                 * 的删除了节点,是的话则调用addCount()将元素数量减一。然后结束方法。
                 */
                if (validated) {
                    //oldVal不为NULL,说明操作成功。
                    if (oldVal != null) {
                        //value == null 说明这是一次删除操作,更新节点个数
                        if (value == null)
                            addCount(-1L, -1);
                        //返回旧值
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }

方法流程总结:

  1. remove底层调用的是replaceNode方法,传入的后两个参数都是NULL,表示当前的替换操作是删除。
  2. 计算hash值,自旋去尝试删除
  3. 寻址后桶位上没有元素,返回NULL。
  4. 桶位上是链表,则遍历链表查找元素进行删除.
  5. 桶位上是fwd节点,表示正在扩容,先尝试帮助扩容,然后继续循环尝试删除
  6. 桶位上形成了红黑树,遍历树查找元素,然后删除,删除后树中元素节点数较少,则退化为链表
  7. 如果确实删除了元素 (非value替换),调用addCount()将元素个数-1,
  8. 删除过程加锁(sycnhronized)锁定当前桶位的头结点。

最后

以上就是俏皮小笼包为你收集整理的ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析的全部内容,希望文章能够帮你解决ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(50)

评论列表共有 0 条评论

立即
投稿
返回
顶部