ConcurrentHashMap源码解析5.get()&remove()方法

get()方法

流程总结

1.计算key的hash值寻址到指定桶位

2.当前桶位没有元素直接返回NULL。

3.当前桶位如果是fwd节点或者是红黑树节点,则调用各自的find()方法。

4.当前桶位不是fwd节点也不是红黑树节点,则遍历桶位,进行查找。

5.查找元素没有任何的加锁操作。

源码解析

     /*
      * 根据key进行查找获取value值
      */     
     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());
        
        /*
         *  进入if语句内部的条件
         *  条件一: table不为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方法进行查找(多态) 
             */
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            
            /*
             * 当前桶位是一个链表,直接遍历每一个节点找到一个key完全相同的节点
             * 找到返回value即可。
             */
            while ((e = e.next) != null) {
    
    
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

ForwardingNode的find()方法

上面在调用get()方法进行查找的时候,如果查找到当前桶位的节点的hash值为-1,说明当前桶位的可能是fwd节点或者是红黑树节点,就要到指定的结构中去查找,这里我们分析一下当hash值为-1时,去ForwardingNode节点查找的方法,关于去红黑树中查找的源码解析,放到后续的TreeBin的源码解析中讲解。

流程总结

1.直接去nextTable中查询数据,经过寻址算法后发现当前桶位没有元素,(可能原桶位中的元素都是经过扩容后都是高(低)位,而当前寻址得到的桶位刚好相反)

2.如果发现当前桶位还是fwd节点 (可能高并发下又发生了扩容) 那么继续去新表查询。

3.如果发现当前桶位是红黑树,那么调用红黑树的find方法去查询

4.否则当前是链表,遍历链表查询即可。

5.find()方法没有加锁过程

        Node<K,V> find(int h, Object k) {
    
    
            /*
             * tab一定不为NULL。(详细解析见transfer方法)
             * 将新表赋值给tab
             */
   			Node<K,V>[] tab = nextTable; 
            
            //自旋
            outer: for (;;) {
    
    
                
                /*
                *  e 表示新表中寻址算法得到的桶位头结点
                *  n 表示新表的长度 
                */
                Node<K,V> e; int n;
                
                /*
                 *  条件1 2 3 永远不成立
                 *  条件4.
                 *  true -> 根据hash值去新表中拿数据时,新表中的桶位有数据 
                 *  false -> 新表的目标桶位为NULL.
                 */
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null; //新表桶位没有数据 直接返回NULL.
                
                
                /*
                 * 前置条件: 扩容后的表,对应hash的桶位不是NULL,e为此桶位的头结点
                 * e可能是哪些类型
                 * 1.node类型
                 * 2.TreeBin类型
                 * 3.FWD类型(高并发下扩容后再次触发扩容)
                 */
                
                //自旋
                for (;;) {
    
    
                    /*
                     * eh:新扩容后表指定桶位的当前节点的hash 
                     * ek:新扩容后表指定桶位的当前节点的key
                     */
                    int eh; K ek; 
                    
                    /*
                     * 当前桶位查找成功
                     */
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    
                    /*
                     * eh < 0
                     *  1.可能是TreeBin节点
                     *  2.可能是FWD节点(新扩容后的表,可能在并发很大的情况下,再次扩容) 
                     */
                    if (eh < 0) {
    
    
                        //fwd节点 继续进行外层循环,继续在新表中查找。
                        if (e instanceof ForwardingNode) {
    
    
                            //在新的fwd节点中拿到新表
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        //调用红黑树节点的查询。
                        else
                            return e.find(h, k);
                    }
                    
                    //遍历到链表末尾也没有找到目标节点,返回NULL.
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }
    }

remove方法

流程总结

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

Guess you like

Origin blog.csdn.net/qq_46312987/article/details/121557743