ConcurrentHashMap源码解析6.TreeBin类

1.回顾

我们知道,当一个桶位形成了红黑树时,此时桶位中存的是一个TreeBin节点,其内部存在两个引用,分别是指向双向链表的first指向红黑树根节点的r

在这里插入图片描述

2.属性

   
    /*
     *  TreeBin继承了Node。其也有hash值(为-2)
     */

   static final class TreeBin<K,V> extends Node<K,V> {
    
    
     
        //红黑树根节点
        TreeNode<K,V> root;
     
        //双向链表的头结点
        volatile TreeNode<K,V> first;
     
        //等待者线程(当前lockState是读锁状态)
        volatile Thread waiter;
        
        /*
         *  1.写锁状态,写是独占状态,以散列表来看,真正进入到TreeBin中的写线程,同一时一			 
         *  只有一个线程。 lockState = 1;
         *  
         *  2.读锁状态,读锁是共享,同一时刻可以有多个线程同时进入到TreeBin对象中获取数据
         *     每一个读线程都会给lockState + 4;
         *  
         *  3.等待者状态(写线程在等待),当TreeBin中有读线程目前正在读取数据时,写线程无法		
         *    修改数据,那么就将lockState的最低两位设置为 10(二进制)
         */
        volatile int lockState;
        
     	
        static final int WRITER = 1; // set while holding write lock
        static final int WAITER = 2; // set when waiting for write lock
        static final int READER = 4; // increment value for setting read lock
     

3.treeifyBin方法

前面我们在分析put方法时,当我们成功的添加了数据时,会检查链表的长度是否大于8,大于的话会调用treeifyBin()方法。

  • TreeBin的成员方法,将链表转换为红黑树的方法.

流程总结

1.先检查当前哈希表的长度是否达到了MIN_TREEIFY_CAPACITY(64),如果没有达到,则会去尝试扩容而不会将当前桶位转为红黑树。

2.第一步会先将当前桶位的头节点锁定(synchronized),然后遍历链表,将原Node链表转换为TreeNode形成的双向链表

3.最后通过传入双向链表的头结点构造一个TreeBin节点 (调用TreeBin的构造方法),放到当前的桶位上。

源码解析

    private final void treeifyBin(Node<K,V>[] tab, int index) {
    
    
             
        /*
         *  b 桶位的头节点
         *  n 哈希表的长度
         *  sc sizeCtl的值
         */
        Node<K,V> b; int n, sc;
        if (tab != null) {
    
    
            
            //数组长度未达到树化的最小长度(64),此时不进行树化,尝试去扩容
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                tryPresize(n << 1);
            
            /*
             * 条件成立,说明当前桶位有数据,且是普通Node节点。
             * (hash值大于等于0,说明就是普通Node节点)
             */
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
    
    
                
                //锁定当前桶位(锁对象是桶位的头元素)
                synchronized (b) {
    
    
                    
                    //再次判断,防止多线程下加锁对象已经被修改
                    if (tabAt(tab, index) == b) {
    
    
                        
                        
                        /*
                         *  接下来就是将普通的单向Node链表转换为双向的TreeNode链表
                         *  为了便于树化
                         *  hd 指向双向链表的头几点,tl指向双向链表的尾结点
                         */
                        TreeNode<K,V> hd = null, tl = null;
                        for (Node<K,V> e = b; e != null; e = e.next) {
    
    
                            TreeNode<K,V> p =
                                new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        /*
                         * 将桶位上的原单向链表构造成一个TreeBin对象
                         * 将双向链表(TreeNode)的头结点传入,构造出一颗红黑树。
                         * 将构造出来的TreeBin节点放到当前桶位上。
                         */
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

4.构造方法

接着上面的treeifyBin()方法,传入一个双向链表构造出一颗红黑树。

过程

  • 先调用父类Node的构造器设置TreeBin节点的hash值为-2,然后TreeBin内部的first属性引用传来的双向链表的头节点。
  • 然后遍历双向链表,根据节点的hash值,hash值比当前节点小的放到往左子树插,反之往右子树插。最终构造出一颗红黑树。
  • 将构造出来的红黑树的根节点赋值给TreeBin内部的root引用。
      /*
       * 将双向TreeNode链表转换为一颗红黑树
       * 传来的这个参数b是构造成的一个双向链表的头结点
       */ 	   
      TreeBin(TreeNode<K,V> b) {
    
    
            //调用父类的构造器,只设置了自己的hash值为-2,(TREEBIN = -2)
            super(TREEBIN, null, null, null);
          
            //使用first引用TreeNode链表。
            this.first = b;
          
            //r就是构造后的红黑树的根节点
            TreeNode<K,V> r = null;
            
            /*
             *  x表示遍历的当前节点 
             *  next表示x的next节点
             */
            for (TreeNode<K,V> x = b, next; x != null; x = next) {
    
    
                //拿到next节点
                next = (TreeNode<K,V>)x.next;
           		
                //强制设置当前插入节点的左右子树为NULL。
                x.left = x.right = null;
                
                /*
                 *  第一个节点进来就是根节点。
                 *  条件成立,说明当前红黑树是一个空树, 
                 *  那么设置插入元素为根节点
                 */
                if (r == null) {
    
    
                    //根节点的父节点为NULL
                    x.parent = null;
                    //红黑树的根节点是黑色
                    x.red = false;
                    //r引用x的对象 (根节点的对象)
                    r = x;
                }
			    
                //非第一个节点进来,此时已经有根节点了。
                else {
    
    
                    
                    //k 表示插入节点的key
                    K k = x.key;
                    
                    //h表示插入节点的hash
                    int h = x.hash;
                    
                    //kc 表示插入节点key的Class类型
                    Class<?> kc = null;
                    
                    //表示为查找插入节点的父节点的一个临时节点
                    TreeNode<K,V> p = r;
                    
                    //自旋
                    for (;;) {
    
    
                        
                        /*
                         * dir(两种值 -1, 1)
                         *  -1表示插入节点的hash值大于当前p节点的hash
                         *  1表示插入节点的hash值小于当前p节点的hash
                         *  ph 表示查找过程中p的hash值。
                         */
                        int dir, ph;
                        
                        //临时节点p的key。
                        K pk = p.key;
                     
                     //--- 下面三种情况都是为dir赋值
                     /*
                      * CASE1
                      * 待插入节点的hash值小于当前节点
                      * 待插入节点可能需要插入到当前节点的左子节点 
                      * 或 继续在左子树上查找
                      */
                     if ((ph = p.hash) > h)   
                            dir = -1; // dir设置为 -1 
                        
                     /*
                      * CASE2
                      * 待插入节点的hash值大于当前节点
                      * 待插入节点可能需要插入到当前节点的右子节点 
                      * 或 继续在右子树上查找
                      */  
                      else if (ph < h)
                            dir = 1; // dir设置为1
                    
                     /*
                      * CASE3
                      * 待插入节点hash值与当前节点hash值一致,会在case3做出最终排序,					   
                      * 最终拿到的dir一定不是0,一定是(-1,或1)
                      */
                      else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);
                        
                      
                      //xp 表示的是插入节点的父节点
                      TreeNode<K,V> xp = p;
                      
                      /*
                       * 根据dir最终的值表明向左或者向右搜索插入位置
                       * 
                       * 条件成立,说明当前p节点即为插入节点的父节点,即当前节点就是叶子节点,可以将节点插入进去
                       * 
                       * 条件不成立,说明p节点下面还有及诶单,需要将p继续向下搜索。
                       */
                      if ((p = (dir <= 0) ? p.left : p.right) == null) {
    
    
                          
                            //设置插入节点的父节点为当前节点
                            x.parent = xp;
                          
                            //判断待插入节点应该插入到当前节点的左儿子或者右儿子上。
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            
                            //插入节点后,红黑树的性质可能被破坏,需要调用平衡方法。
                            r = balanceInsertion(r, x);
                            break;
                        }
                    }
                }
            }
            //将r赋值给TreeBin对象的root引用。
            this.root = r;
            assert checkInvariants(root);
        }

5.find方法

     /*
      *  h表示查找元素的hash值
      *  k 表示查找元素的key
      */			
	 final Node<K,V> find(int h, Object k) {
    
    
         
            if (k != null) {
    
    
                
                /*
                 * e表示循环迭代的当前节点
                 * 迭代的是first引用的链表。
                 */
                for (Node<K,V> e = first; e != null; ) {
    
    
                    
                    /*
                     *  s保存的是lock的临时状态
                     *  ek 链表节点当前节点的key
                     */
                    int s; K ek;
                    
                    //条件成立表示当前TreeBin有等待者线程 或者 有写操作线程正在加锁
                    if (((s = lockState) & (WAITER|WRITER)) != 0) {
    
    
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        e = e.next;
                    }
                    
                    /*
                     *  前置条件: 当前TreeBIn没有等待者线程或写线程
                     *   
                     *  这里CAS尝试添加读锁,锁状态+4
                     */
                    else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                        s + READER)) {
    
    
                        
                        /*
                         *  r 红黑树根节点
                         *  p 最终查询到的节点
                         */
                        TreeNode<K,V> r, p;
                        try {
    
    
                            p = ((r = root) == null ? null :
                                 r.findTreeNode(h, k, null)); //查询
                            
                        } finally {
    
    
                            Thread w;
                            
                            /*
                             * 当前线程查询红黑树结束,尝试释放当前线程的读锁,就是让
                             * lockState - 4
                             *
                             *  (READER|WAITER) = 1010 表示当前只有一个线程在读,且
                             *  只有一个线程在等待 当前读线程为TreeBin中的最后一个读线程
                             *
                             *  (w = waiter) != null 说明有一个写线程在等待读操作全部结束
                             *  
                             *  大致意思就是说,当前的这个读线程是最后一个读线程,并且发现当前有写线程在等待(阻塞),
                             *  此时的这个读线程读完之后需要将写线程唤醒。
                             */
                            if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                                (READER|WAITER) && (w = waiter) != null)
                                //unpark唤醒写线程,
                                LockSupport.unpark(w);
                        }
                        return p;
                    }
                }
            }
            return null;
        }

Guess you like

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