ConcurrentHashMap静态公共方法分析


文章目录

  • ConcurrentHashMap静态公共方法分析
  • 1. spread 方法
  • 2. tabAt 方法
  • 3. casTabAt 方法
  • 4. setTabAt 方法
  • 5. resizeStamp 方法
  • 6. tableSizeFor 方法



分析ConcurrentHashMap中一些重要的公共方法,以便理解后面的源码。

1. spread 方法

spread()方法的作用是为了将hash值变得更加散列,从而减少hash冲突的产生。

翻译过来的方法注释:

“将哈希值较高的位传播到较低的位,并强制顶位为0。因为表使用2的次方掩码,所以仅在当前掩码以上的位上变化的散列集总是会碰撞。(已知的例子是在小表中保存连续整数的Float键集。)所以我们应用一个变换,向下传播更高位的影响。比特传播的速度、效用和质量之间需要权衡。因为许多常见的散列集已经合理分布(因此不会从扩散中受益),而且因为我们使用树来处理箱子中的大型碰撞集,所以我们只是以尽可能便宜的方式对一些移位的位进行XOR,以减少系统损失,以及合并由于表边界而永远不会在索引计算中使用的最高位的影响。”

/**
 * 计算Node节点hash值的算法:参数h为hash值
 * 例如:
 * h二进制为 --> 	 		 		 1100 0011 1010 0101 0001 1100 0001 1110
 * (h >>> 16) -->  					0000 0000 0000 0000 1100 0011 1010 0101 
 * (h ^ (h >>> 16)) -->				1100 0011 1010 0101 1101 1111 1011 1011
 * 注:(h ^ (h >>> 16)) 目的是让h的高16位也参与寻址计算,使得到的hash值更分散,减少hash冲突产生
 * ------------------------------------------------------------------------------
 * HASH_BITS -->					0111 1111 1111 1111 1111 1111 1111 1111
 * (h ^ (h >>> 16)) -->				1100 0011 1010 0101 1101 1111 1011 1011
 * (h ^ (h >>> 16)) & HASH_BITS --> 0100 0011 1010 0101 1101 1111 1011 1011
 * 注: (h ^ (h >>> 16))得到的结果再& HASH_BITS,目的是为了让得到的hash值结果始终是一个正数
 */
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

2. tabAt 方法

tabAt()方法用于获取 Node 数组 table(桶)指定下标位置上的 Node 节点。

ASHIFT = 31 - Integer.numberOfLeadingZeros(scale),在静态代码块中ASHIFT被赋值,ConcurrentHashMap采用了位运算的方式来计算数组中元素的偏移地址而不是用乘法,这样会更加高效。

/**
 * 获取 Node 数组 table 中下标为 i 的 Node 节点
 * tab Node数组引用
 * i 数组下标
 */
@SuppressWarnings("unchecked")
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    // getObjectVolatile() 是native方法,作用是从主存中获取线程共享变量
    // ((long)i << ASHIFT) + ABASE 通过运用位运算计算出目标节点的偏移地址
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

3. casTabAt 方法

/**
 * cas方式将table中的元素更新
 * tab Node数组table(哈希表)
 * i 要替换的元素的下标
 * c 期望节点值
 * v 新的节点值
 */
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    // 调用Unsafe类的compareAndSwapObject()方法
    // 与tabAt方法一样计算出元素的偏移地址
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

4. setTabAt 方法

/**
 *
 * tab Node数组table(哈希表)
 * i table数组下标
 * v 要设置的值
 */
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    // 调用Unsafe中的putObjectVolatile()方法,设置主存中变量的值
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

5. resizeStamp 方法

/**
 * 返回用于调整大小为n的表大小的戳位。当被RESIZE_STAMP_SHIFT左移时必须为负。
 */
static final int resizeStamp(int n) {
    return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

举例分析:

假设table需要冲16扩容到32时,numberOfLeadingZeros(16)的返回值就是27。

RESIZE_STAMP_BITS的值是16,经过(1 << (RESIZE_STAMP_BITS - 1)运算后再与27做与运算,就会得到一个扩容表示戳。

6. tableSizeFor 方法

tableSizeFor与HashMap中的tableSizeFor方法一样,传入一个数,通过位运算返回大于等于这个数的最小2次幂的数。

private static final int tableSizeFor(int c) {
    int n = c - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}