ConcurrentHashMap红黑树自平衡时的读操作

ConcurrentHashMap红黑树自平衡时的读操作

问题描述

A线程使用put方法修改ConcurrentHashMap,根据hashCode得到指定位置的Node节点Node[i],这个位置的Node类型是TreeBin,即红黑树。此时插入操作需要修改红黑树结构(左旋/变色),同时线程B同样访问到这个节点,会阻塞吗?不阻塞的话那怎么保证遍历结果的正确性?

ConcurrentHashMap读方法get

  • 想要解决这个疑问,让我们先来看看ConcurrentHashMap的get方法:
    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
            	// ForwardingNode或TreeBin节点find
                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;
    }
  • 其中当hash值小于0时,调用了node的find方法,ConcurrentHashMap中ForwardingNode和TreeBin的hash小于0,因此会调用TreeBin的对象的find方法,这个方法就是遍历红黑树的方法
 final Node<K,V> find(int h, Object k) {
            if (k != null) {
                for (Node<K,V> e = first; e != null; ) {
                    int s; K ek;
                    // 已经被加锁或被占有,退化为线性遍历 查询时间复杂度O(n)
                    // 如果写锁释放,立即使用红黑树遍历
                    if (((s = lockState) & (WAITER|WRITER)) != 0) {
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        e = e.next;
                    }
                    // 加读锁后使用红黑树遍历 查询时间复杂度O(lgN)
                    else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                                                 s + READER)) {
                        TreeNode<K,V> r, p;
                        try {
                            p = ((r = root) == null ? null :
                                 r.findTreeNode(h, k, null));
                        } finally {
                            Thread w;
                            if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                                (READER|WAITER) && (w = waiter) != null)
                                LockSupport.unpark(w);
                        }
                        return p;
                    }
                }
            }
            return null;
        }
  • 从上面的代码中,我们不难发现,从根节点开始查找的时候会先判断是是否当前状态是否被写锁定
  • 如果当前有线程正在写,此时退化为线性链表,顺序遍历判断下一个节点;在顺序遍历完之前如果写锁释放并且CAS设置读标志成功,则使用红黑树遍历搜索

这里有个知识点需要了解的是TreeNode其实同时又是一个双向链表

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


 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

也正是因为双向链表的结构,在扩容transfer的时候对于红黑树节点,可以用双向节点进行遍历,将红黑树根据rehash后的值,split分为两个链表,再将分割后的两个链表赋值给新数组的新位置上,这里的链表根据长度有以下两种情况

  • 长度小于等于6,untreeify为普通的链表
  • 长度大于6,treeifyBin为一个红黑树

  • private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)
else if (f instanceof TreeBin) {
     TreeBin<K,V> t = (TreeBin<K,V>)f;
     TreeNode<K,V> lo = null, loTail = null;
     TreeNode<K,V> hi = null, hiTail = null;
     int lc = 0, hc = 0;
     for (Node<K,V> e = t.first; e != null; e = e.next) {
         int h = e.hash;
         TreeNode<K,V> p = new TreeNode<K,V>
             (h, e.key, e.val, null, null);
         if ((h & n) == 0) {
             if ((p.prev = loTail) == null)
                 lo = p;
             else
                 loTail.next = p;
             loTail = p;
             ++lc;
         }
         else {
             if ((p.prev = hiTail) == null)
                 hi = p;
             else
                 hiTail.next = p;
             hiTail = p;
             ++hc;
         }
     }
     ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
         (hc != 0) ? new TreeBin<K,V>(lo) : t;
     hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
         (lc != 0) ? new TreeBin<K,V>(hi) : t;

写锁

写锁是在对红黑树进行修改自平衡时加上的

final TreeNode<K,V> putTreeVal(int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                if (p == null) {
                    first = root = new TreeNode<K,V>(h, k, v, null, null);
                    break;
                }
                else if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }

                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    TreeNode<K,V> x, f = first;
                    first = x = new TreeNode<K,V>(h, k, v, f, xp);
                    if (f != null)
                        f.prev = x;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    if (!xp.red)
                        x.red = true;
                    else {
                    	// 	加写锁
                        lockRoot();
                        try {
                            root = balanceInsertion(root, x);
                        } finally {
                        	// 释放锁
                            unlockRoot();
                        }
                    }
                    break;
                }
	 private final void lockRoot() {
            if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
                contendedLock(); // offload to separate method
        }
        
     private final void contendedLock() {
            boolean waiting = false;
            for (int s;;) {
                if (((s = lockState) & ~WAITER) == 0) {
                    if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
                        if (waiting)
                            waiter = null;
                        return;
                    }
                }
                else if ((s & WAITER) == 0) {
                    if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
                        waiting = true;
                        waiter = Thread.currentThread();
                    }
                }
                else if (waiting)
                    LockSupport.park(this);
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值