ConcurrentHashMap(JDK1.8)中红黑树的实现

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ConcurrentHashMap(JDK1.8)中红黑树的实现相关的知识,希望对你有一定的参考价值。

ConcurrentHashMap(JDK1.8)中红黑树的实现

文章目录


本文只介绍红黑树的实现 不对并发容器ConcurrentHashMap进行介绍。

红黑树的理论和基础试下可以阅读红黑树与JAVA实现

红黑树特性

  1. 每个结点的或是黑色或是红色
  2. 根结点是黑色
  3. 每个叶子结点都是黑色(NIL)
  4. 如果一个结点是红色,那么它的子结点必须是黑色
  5. 对任意一结点,该结点到其叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点

ConcurrentHashMap数据结构示意图

代码实现分析

结点组成

Node<K,V>

        // key的hash值
        final int hash;
        // 关键字
        final K key;
        // 存储值
        volatile V val;
        // 下一个结点
        volatile Node<K,V> next;

TreeNode<K,V>
作为红黑树结构的存储结构,比一般红黑树存储结构出来的next和prev,可以将这些结点变成双向的链表结构,是为了方便从链表变为红黑树,在从红黑树变成链表。在ConcurrentHashMap中,链表与红黑树的转变是依据链表中的结点数量,默认变成红黑树的链表结点个数需要大于8。

hashkeyvalnextprevparentleftrightred
static final class TreeNode<K,V> extends Node<K,V> 
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) 
        super(hash, key, val, next);
        this.parent = parent;
    

    Node<K,V> find(int h, Object k) 
        return findTreeNode(h, k, null);
    

    /**
     * Returns the TreeNode (or null if not found) for the given key
     * starting at given root.
     */
    final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) 
        if (k != null) 
            TreeNode<K,V> p = this;
            do  
                int ph, dir; K pk; TreeNode<K,V> q;
                TreeNode<K,V> pl = p.left, pr = p.right;
                if ((ph = p.hash) > h)
                    p = pl;
                else if (ph < h)
                    p = pr;
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                else if (pl == null)
                    p = pr;
                else if (pr == null)
                    p = pl;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                else if ((q = pr.findTreeNode(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
             while (p != null);
        
        return null;
    

TreeBin<K,V>

继承Node,树标记结构,表明该结点背后有一棵红黑树。在新增结点和删除结点的时候,为了并发的安全都需要会进行锁的竞争。root和first不应该是同一个结点。

        TreeNode<K,V> root; // 树根结点
        volatile TreeNode<K,V> first; // 首结点
        volatile Thread waiter; // 等待的线程
        volatile int lockState; // 结点锁的情况

主要方法

构造函数

从给定的b结点开始,遍历整个链表,构建红黑树。

TreeBin(TreeNode<K,V> b) 
    super(TREEBIN, null, null, null);
    // 参数b的值为首结点
    this.first = b;
    // 根结点
    TreeNode<K,V> r = null;
    // 从首结点开始遍历链表
    for (TreeNode<K,V> x = b, next; x != null; x = next) 
        // 获取下一个结点
        next = (TreeNode<K,V>)x.next;
        // 初始化x的左右子结点 置null
        x.left = x.right = null;
        // 设置根结点,颜色为黑色
        if (r == null) 
            x.parent = null;
            x.red = false;
            r = x;
        
        else 
            // 结点的关键字
            K k = x.key;
            // 结点的hash
            int h = x.hash;
            Class<?> kc = null;
            // 遍历以r为根的树 
            for (TreeNode<K,V> p = r;;) 
                int dir, ph;
                K pk = p.key;
                // dir-1 左子 dir1 右子
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    // 当 hashCodes 相等且不可比较时,用于排序插入的打破平局实用程序。 我们不需要总排序,只需要一致的插入规则来保持重新平衡之间的等价性。 简化了比较平局的处理逻辑。
                    dir = tieBreakOrder(k, pk);
                    TreeNode<K,V> xp = p;
                // p作为x的双亲结点,确认x是双亲结点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;
                
            
        
    
    this.root = r;
    assert checkInvariants(root);

新增节点修复红黑树方法

balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x)

root为根,x为新增的那个结点,新增的结点都为红色。

    static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) 
        // 先将x变为红结点
        x.red = true;
        // x的双亲结点xp
        // x的祖父结点xpp
        // x的祖父结点xpp的左子结点xppl
        // x的祖父结点xpp的右子结点xppr
        for (TreeNode<K,V> xp, xpp, xppl, xppr;;) 
            // 若x的父结点为空,则将节点置为黑,直接返回x即为根结点退出for。
            if ((xp = x.parent) == null) 
                x.red = false;
                return x;
            
            // 此时父节点不为null,若父节点是黑色的,那么不要调整,直接返回
            // 若当前结点的祖父节点为空(说明父节点是根结点),直接返回根结点。
            else if (!xp.red || (xpp = xp.parent) == null)
                return root;
            // 目前父节点是红色的 若父节点是祖父的左子结点 
            if (xp == (xppl = xpp.left)) 
                // 叔叔结点是红色
                // 将双亲节点和叔叔结点变黑,祖父变红
                // x指向祖父结点
                if ((xppr = xpp.right) != null && xppr.red) 
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                
                // 以下叔叔结点是黑色
                else 
                    // 当前结点是双亲结点的右子
                    if (x == xp.right) 
                        //当前x指向双亲结点 ,以双亲节点为根进行左旋
                        root = rotateLeft(root, x = xp);
                        // 取x的祖父结点
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    
                    if (xp != null) 
                        // 当前结点是双亲结点的左子
                        // 双亲结点变黑
                        xp.red = false;
                        // 若还有祖父结点,将祖父结点变红,以祖父结点为支点进行右旋
                        if (xpp != null) 
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        
                    
                
            
            // 目前父节点是红色的 若父节点是祖父的右子结点
            else 
                // 叔叔结点是红色
                // 将双亲节点和叔叔结点变黑,祖父变红
                // x指向祖父结点
                if (xppl != null && xppl.red) 
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                
                 // 以下叔叔结点是黑色
                else 
                    // 当前结点是双亲结点的左子
                    if (x == xp.left) 
                        //当前x指向双亲结点 ,以双亲节点为根进行右旋
                        root = rotateRight(root, x = xp);
                        // 取x的祖父结点
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    
                    if (xp != null) 
                        // 当前结点是双亲结点的右子
                        // 双亲结点变黑
                        xp.red = false;
                        // 若还有祖父结点,将祖父结点变红,以祖父结点为支点进行左旋
                        if (xpp != null) 
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        
                    
                
            
        
    

左旋方法

        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) 
            // r右子结点 pp是p的双亲结点 rl是r的左子结点
            TreeNode<K,V> r, pp, rl;
            // 若p是空 或者p没有右子 不进行左旋
            if (p != null && (r = p.right) != null) 
                // 若r存在左子,则变为p的右子
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                // 若p的双亲结点为空(p是当前的根结点),则r变为根结点,r颜色变黑
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                // 目前p的双亲结点不为空,若p是双亲结点的左子,将r变为双亲结点的左子
                else if (pp.left == p)
                    pp.left = r;
                // 若p是双亲结点的右子,将r变为双亲结点的右子
                else
                    pp.right = r;
                // r的左子变为p,p的双亲结点变为r
                r.left = p;
                p.parent = r;
            
            // 旋转结束,返回实际根结点
            return root;
        

右旋方法

        static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) 
            // l是左子结点 pp是p的双亲结点 lr是l的右子结点
            TreeNode<K,V> l, pp, lr;
             // 若p是空 或者p没有左子 不进行右旋
            if (p != null && (l = p.left) != null) 
                // 若l存在右子,则p变为l的左子
                if ((lr = p.left = l.right) != null)
                    lr.parent = p;
                // 若p的双亲结点为空(p是当前的根结点),则l变为根结点,l颜色变黑 
                if ((pp = l.parent = p.parent) == null)
                    (root = l).red = false;
                // 目前p的双亲结点不为空,若p是双亲结点的右子,将l变为双亲结点的右子 
                else if (pp.right == p)
                    pp.right = l;
                // 若p是双亲结点的左子,将r变为双亲结点的左子
                else
                    pp.left = l;
                // l的右子变为p,p的双亲结点变为r
                l.right = p;
                p.parent = l;
            
            // 旋转结束,返回实际根结点
            return root;
        

红黑树特性检查方法

static <K,V> boolean checkInvariants(TreeNode<K,V> t) 
    // t 从根开始遍历的结点
    // tp 为t的双亲结点
    // tl 为t的左子
    // tr 为t的右子
    // tb 为t的前驱
    // tn 为t的后继
    TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
        tb = t.prev, tn = (TreeNode<K,V>)t.next;
    // 前驱的后继不是本身--链表结点关系出现混乱
    if (tb != null && tb.next != t)
        return false;
    // 后继的前驱不是本身--链表结点关系出现混乱
    if (tn != null && tn.prev != t)
        return false;
    // t 不是双亲结点的左右子--树结点关系出现混乱
    if (tp != null && t != tp.left && t != tp.right)
        return false;
    // t的左子的双亲结点不是t,或者左子的hash值大于t的hash--树结点关系出现混乱
    if (tl != null && (tl.parent != t || tl.hash > t.hash))
        return false;
    // t的右子的双亲结点不是t,或者右子的hash值小于t的hash--树结点关系出现混乱
    if (tr != null && (tr.parent != t || tr.hash < t.hash))
        return false;
    // t是红色 且左右子也是红色 if ((t.red && tl != null && tl.red)||(t.red && tr != null && tr.red))???
    // 这个是说明结点是红色,其左右子不可以同时为红色,不应该是不论哪个子结点为红色也不可以吗?
    if (t.red && tl != null && tl.red && tr != null && trJDK1.8中的ConcurrentHashMap

ConcurrentHashMap 源码详细分析(JDK1.8)

ConcurrentHashMap(JDK1.8)中红黑树的实现

JUC系列并发容器之ConcurrentHashMap(JDK1.8版)

ConcurrentHashMap(JDK1.8)为什么要放弃Segment

多线程-ConcurrentHashMap(JDK1.8)