ConcurrentHashMap 源码解析(三):get()

本文重点解析ConcurrentHashMap.get()方法,及其代码中包含的所有涉及到的子方法。其中,spread()和find()是比较复杂的两个方法,下面会重点讲解。

/**
  * Returns the value to which the specified key is mapped,
  * or {@code null} if this map contains no mapping for the key.
  *
  * <p>More formally, if this map contains a mapping from a key
  * {@code k} to a value {@code v} such that {@code key.equals(k)},
  * then this method returns {@code v}; otherwise it returns
  * {@code null}.  (There can be at most one such mapping.)
  *
  * @throws NullPointerException if the specified key is null
  */
 public V get(Object key) {
     //tab: table引用
     //e: 游标节点
     //p: 查找到的节点
     //n: table长度
     //eh: 游标节点hash值
     //ek: 游标节点key
     Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
     //获取rehash后的hash值
     int h = spread(key.hashCode());
     //true -> 当前table不为空 且 在桶位上匹配到节点
     if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
         //指定桶位上的节点hash值与需查找的节点的hash值相等
         if ((eh = e.hash) == h) {
             //true -> 表示需要查找的节点就是该桶位上的头结点
             if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                 //返回匹配到的桶位元素的value
                 return e.val;
         }
         //指定桶位上的节点hash值是负数 说明当前桶位是 FWD桶位 或 TreeBin桶位
         else if (eh < 0)
             //根据e节点所属的类型(FWD or TreeBin) 调用各自重写的find方法,并返回匹配到的节点的value
             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;
 }

spread()方法的作用是将key的hash值再rehash一次,将高16位也参与到计算中,保证得到的值更加散列。

/**
  * Spreads (XORs) higher bits of hash to lower and also forces top
  * bit to 0. Because the table uses power-of-two masking, sets of
  * hashes that vary only in bits above the current mask will
  * always collide. (Among known examples are sets of Float keys
  * holding consecutive whole numbers in small tables.)  So we
  * apply a transform that spreads the impact of higher bits
  * downward. There is a tradeoff between speed, utility, and
  * quality of bit-spreading. Because many common sets of hashes
  * are already reasonably distributed (so don't benefit from
  * spreading), and because we use trees to handle large sets of
  * collisions in bins, we just XOR some shifted bits in the
  * cheapest possible way to reduce systematic lossage, as well as
  * to incorporate impact of the highest bits that would otherwise
  * never be used in index calculations because of table bounds.
  *
  * HASH_BITS 将得出的结果取正数
  * 其它操作都与hashmap一致,唯一不同就是最后结果要取正
  *
  * h:           1100 0011 1010 0101 0001 1100 0001 1110
  * h >>> 16:    0000 0000 0000 0000 1100 0011 1010 0101
  * ^:           1100 0011 1010 0101 1101 1111 1011 1011
  * HASH_BITS:   0111 1111 1111 1111 1111 1111 1111 1111
  * &:           0100 0011 1010 0101 1101 1111 1011 1011
  */
 static final int spread(int h) {
     return (h ^ (h >>> 16)) & HASH_BITS;
 }

e.find()方法需要根据调用对象e的类型(ForwardingNode 或者 TreeBin)决定调用的是哪个find()方法。

1、ForwardingNode.find()

/**
  * A node inserted at head of bins during transfer operations.
  */
 static final class ForwardingNode<K,V> extends Node<K,V> {
     //新表的引用
     final Node<K,V>[] nextTable;
     ForwardingNode(Node<K,V>[] tab) {
         //hash值固定为-1
         super(MOVED, null, null, null);
         this.nextTable = tab;
     }

     Node<K,V> find(int h, Object k) {
         // loop to avoid arbitrarily deep recursion on forwarding nodes
         //获取nextTable引用
         //注意: 如果当前访问的节点是FWD结点,说明数组正在进行扩容,因此会去nextTable获取数据
         Node<K,V>[] tab = nextTable;
         //外层自旋
         outer: for (;;) {
             //e: 游标节点
             //n: table数组长度
             Node<K,V> e; int n;
             if (k == null || //传入的key为null
                 tab == null || //nextTable为null
                 (n = tab.length) == 0 || //nextTable中没数据
                 (e = tabAt(tab, (n - 1) & h)) == null) //nextTable指定桶位上为null
                 return null;
             //内层自旋
             for (;;) {
                 //eh: 当前游标节点的hash值 初始值是当前桶位上的头结点的hash值
                 //ek: 当前游标节点的key 初始值是当前桶位上的头结点的key
                 int eh; K ek;
                 if ((eh = e.hash) == h && //当前游标节点的hash值与需查找节点的hash值一致
                     ((ek = e.key) == k || (ek != null && k.equals(ek)))) //当前游标节点的key与需查找节点的key一致
                     //返回当前游标节点
                     return e;
                 //当前游标的节点hash值是负数 说明当前桶位是 FWD桶位 或 TreeBin桶位
                 if (eh < 0) {
                     //如果当前桶位节点是FWD节点
                     //为什么上面已经赋值了nextTable 这里还要再赋值一次?防止nextTable再次触发了扩容,导致当前节点是个FWD节点
                     if (e instanceof ForwardingNode) {
                         //将迁移的table赋值给tab
                         tab = ((ForwardingNode<K,V>)e).nextTable;
                         //回到外层自旋,重新开始匹配
                         continue outer;
                     }
                     else
                     //如果当前桶位节点是TreeBin 调用TreeBin.find()
                         return e.find(h, k);
                 }
                 //遍历当前桶位上的链表
                 if ((e = e.next) == null)
                     //如果到末尾还未匹配到就返回null
                     return null;
             }
         }
     }
 }

2、ForwardingNode.find()

/**
  * Returns matching node or null if none. Tries to search
  * using tree comparisons from root, but continues linear
  * search when lock not available.
  */
 final Node<K,V> find(int h, Object k) {
     //h: 需要查询的节点的hash值
     //k: 需要查询的节点的key
     if (k != null) {
         //遍历first指针指向的双向链表
         for (Node<K,V> e = first; e != null; ) {
             //s: lockState临时状态
             //ek: 当前循环节点的key
             int s; K ek;
             //(WAITER|WRITER) -> 0010 | 0001 = 0011
             //lockState & 0011 != 0 true -> 表示后两位不为0,说明有等待者线程 或 有写线程在加锁,需要从链表读取数据
             if (((s = lockState) & (WAITER|WRITER)) != 0) {
                 //true -> 匹配到需要查找的元素
                 if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek))))
                     return e;
                 //e指向下一个元素
                 e = e.next;
             }
             //前置条件: 没有等待者线程 或 没有写线程
             //true -> 添加读锁成功 读锁是共享锁 从红黑树中读取数据
             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 {
                     //w: 表示等待者线程
                     Thread w;
                     //(READER|WAITER) -> 0100 | 0010 = 0110 => 6 表示当前有一个读线程和一个等待中的写线程
                     //U.getAndAddInt(this, LOCKSTATE, -READER) 注意:这里是先get到LOCKSTATE的值与(READER|WAITER)对比后再 -4 释放读锁
                     //当前线程为最后一个读线程
                     //(w = waiter) != null true -> 有写线程在等待读操作全部结束
                     if (U.getAndAddInt(this, LOCKSTATE, -READER) == (READER|WAITER) && (w = waiter) != null)
                         //唤醒等待中的写线程
                         LockSupport.unpark(w);
                 }
                 return p;
             }
         }
     }
     return null;
 }

上面还涉及到TreeNode.findTreeNode()方法,本文也给出详细的解析。

/**
  * 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  {
             //ph: 当前游标节点的hash值
             //dir: 待插入节点位于当前游标节点的 左边 或 右边
             //pk: 当前游标节点的key
             //q: 匹配到的节点
             int ph, dir; K pk; TreeNode<K,V> q;
             //pl: 当前游标节点的左子节点
             //pr: 当前游标节点的右子节点
             TreeNode<K,V> pl = p.left, pr = p.right;
             //CASE1: 当前游标节点的hash值 > 待插入节点的hash值
             if ((ph = p.hash) > h)
                 p = pl;
             //CASE2: 当前游标节点的hash值 < 待插入节点的hash值
             else if (ph < h)
                 p = pr;
             //前置条件: 当前游标节点的hash值 == 待插入节点的hash值
             //CASE3: 当前游标节点的key == 待插入的节点的key
             else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                 return p;
             //前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
             //        2、当前游标节点的key != 待插入的节点的key
             //CASE4: 当前游标节点的左子节点是null
             else if (pl == null)
                 p = pr;
             //前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
             //        2、当前游标节点的key != 待插入的节点的key
             //CASE4: 当前游标节点的右子节点是null
             else if (pr == null)
                 p = pl;
             //前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
             //        2、当前游标节点的key != 待插入的节点的key
             //        3、当前游标节点的左、右节点 != null
             //CASE5: 获取待插入节点的key的class类型 并计算出dir
             else if ((kc != null ||
                       (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0)
                 p = (dir < 0) ? pl : pr;
             //前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
             //        2、当前游标节点的key != 待插入的节点的key
             //        3、当前游标节点的左、右节点 != null
             //        4、无法通过待插入节点key的class类型计算dir
             //CASE6: 遍历当前游标节点的整个右子树
             else if ((q = pr.findTreeNode(h, k, kc)) != null)
                 return q;
             //前置条件: 1、当前游标节点的hash值 == 待插入节点的hash值
             //        2、当前游标节点的key != 待插入的节点的key
             //        3、当前游标节点的左、右节点 != null
             //        4、无法通过待插入节点key的class类型计算dir
             //        5、遍历当前游标节点的整个右子树还是未找到
             //CASE7: 从左子节点开始继续找
             else
                 p = pl;

         } while (p != null);
     }
     return null;
 }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值