Java源码心得jdk8-ConcurrentHashMap三

183 阅读3分钟

原文日期:2017-10-22


在前两篇中已讲了ConcurrentHashMap添加元素和扩容源码的大致实现。接下来继续看get和remove方法。

Java源码心得jdk8-ConcurrentHashMap一

Java源码心得jdk8-ConcurrentHashMap二

addCount方法

该方法主要在为put方法内末尾调用,主要完成两件事情。

1.更新统计元素数量

2.判断是否需要进行扩容

private final void addCount(long x, int check) {
   CounterCell[] as; long b, s;
   //更新元素数量baseCount
   if ((as = counterCells) != null ||
         !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
      CounterCell a; long v; int m;
      boolean uncontended = true;
      if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
         //新增一个CounterCell对象去计数
         fullAddCount(x, uncontended);
         return;
      }
      if (check <= 1)
         return;
      s = sumCount();
   }
   //检查是否需要进行扩容
   if (check >= 0) {
      Node<K,V>[] tab, nt; int n, sc;
      while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
            (n = tab.length) < MAXIMUM_CAPACITY) {
         int rs = resizeStamp(n);
         if (sc < 0) {
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                  sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                  transferIndex <= 0)
               break;
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
               //sizeCtl小于0,表示集合已经在扩容,则直接对nextTable进行扩容
               transfer(tab, nt);
         }
         else if (U.compareAndSwapInt(this, SIZECTL, sc,
               (rs << RESIZE_STAMP_SHIFT) + 2))
            //没有其他现在在扩容,则新增nextTable进行扩容
            transfer(tab, null);
         s = sumCount();
      }
   }
}

get方法

1.通过key计算hash值。

2.通过hash计算index索引值。

3.找到桶数组对应index索引的桶元素。

4.如果桶元素是要找的node节点值,返回node节点value。

5.否则遍历桶元素子节点node(链表或者树结构),寻找key值相等的node节点。

ConcurrentHashMap的get方法和HashMap的get方法实现原理基本相似,ConcurrentHashMap的不同是通过Unsafe的原子操作方法去获取桶元素。

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    //计算hash值
    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)))
                //桶下找到该key值对应的节点,直接返回值
                return e.val;
        }
        else if (eh < 0)
                //hash值为负数,遍历树查找节点
            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;
}

remove方法

1.通过key计算hash值。

2.通过hash计算index索引值。

3.找到桶数组对应index索引的桶元素。如果此时集合正在扩容,则先帮助集合扩容,再去删除节点。

4.如果桶元素是链表节点,则遍历链表删除节点。

5.否则遍历树结构,删除节点。

该方法和HashMap的删除节点的方法实现原理基本差不多,但是ConcurrentHashMap的删除方法多了个帮助集合扩容步骤,并且使用了Unsafe原子方法获取和设置桶元素。

public V remove(Object key) {
    return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
    int hash = spread(key.hashCode());
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0 ||
            (f = tabAt(tab, i = (n - 1) & hash)) == null)
            break;
        else if ((fh = f.hash) == MOVED)
            //删除过程遇到集合正在扩容,则先帮助扩容后,再删除结点
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            boolean validated = false;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        validated = true;
                        for (Node<K,V> e = f, pred = null;;) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                V ev = e.val;
                                if (cv == null || cv == ev ||
                                    (ev != null && cv.equals(ev))) {
                                    oldVal = ev;
                                    if (value != null)
                                        //替换对应节点的值
                                        e.val = value;
                                    else if (pred != null)
                                        //桶元素子节点是要删除的目标,则前一个节点指向目标指向的后一个节点。
                                        pred.next = e.next;
                                    else
                                        //桶元素是要删除的节点,设置后面的元素为桶元素
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            pred = e;
                            if ((e = e.next) == null)
                                break;
                        }
                    }
                    else if (f instanceof TreeBin) {
                        //遍历树,删除节点
                        validated = true;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        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 = pv;
                                if (value != null)
                                    p.val = value;
                                else if (t.removeTreeNode(p))
                                    setTabAt(tab, i, untreeify(t.first));
                            }
                        }
                    }
                }
            }
            if (validated) {
                if (oldVal != null) {
                    if (value == null)
                        addCount(-1L, -1);
                    return oldVal;
                }
                break;
            }
        }
    }
    return null;
}