曹工说JDK源码(3)--ConcurrentHashMap,Hash算法优化、位运算揭秘 (2)

我们仅仅异或了从高位移动下来的二进制位,用最经济的方式,削减系统性能损失,同样,因为数组大小的限制,导致高位在索引计算中一直用不到,我们通过这种转换将其利用起来。

ConcurrentHashMap如何优化

在concurrentHashMap中,其主要是:

final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode());

这里主要是使用spread方法来计算hash值:

static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; }

大家如果要仔细观察每一步的二进制,可以使用下面的demo:

static final int spread(int h) { // 1 String s = Integer.toBinaryString(h); System.out.println("h:" + s); // 2 String lower16Bits = Integer.toBinaryString(h >>> 16); System.out.println("lower16Bits:" + lower16Bits); // 3 int temp = h ^ (h >>> 16); System.out.println("h ^ (h >>> 16):" + Integer.toBinaryString(temp)); // 4 int result = (temp) & HASH_BITS; System.out.println("final:" + Integer.toBinaryString(result)); return result; }

这里和HashMap相比,多了点东西,也就是多出来了:

& HASH_BITS;

这个有什么用处呢?

因为(h ^ (h >>> 16))计算出来的hashcode,可能是负数。这里,和 HASH_BITS进行了相与:

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash 1111 1111 1111 1111 1111 1111 1111 1111 假设计算出来的hashcode为负数,因为第32位为1 0111 1111 1111 1111 1111 1111 1111 1111 0x7fffffff 进行相与 0111 ..................................

​ 这里,第32位,因为0x7fffffff的第32位,总为0,所以相与后的结果,第32位也总为0 ,所以,这样的话,hashcode就总是正数了,不会是负数。

concurrentHashMap中,node的hashcode,为啥不能是负数

当hashcode为正数时,表示该哈希桶为正常的链表结构。

当hashcode为负数时,有几种情况:

ForwardingNode

此时,其hash值为:

static final int MOVED = -1; // hash for forwarding nodes

当节点为ForwardingNode类型时(表示哈希表在扩容进行中,该哈希桶已经被迁移到了新的临时hash表,此时,要get的话,需要去临时hash表查找;要put的话,是不行的,会帮助扩容)

TreeBin static final int TREEBIN = -2; // hash for roots of trees

表示,该哈希桶,已经转了红黑树。

扩容时的位运算 /** * Returns the stamp bits for resizing a table of size n. * Must be negative when shifted left by RESIZE_STAMP_SHIFT. */ static final int resizeStamp(int n) { return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)); }

这里,假设,n为4,即,hashmap中数组容量为4.

下面这句,求4的二进制表示中,前面有多少个0.

Integer.numberOfLeadingZeros(n)

表示为32位后,如下

0000 0000 0000 0000, 0000 0000 0000 0100

所以,前面有29个0,即,这里的结果为29.

(1 << (RESIZE_STAMP_BITS - 1)

这一句呢,其中RESIZE_STAMP_BITS 是个常量,为16. 相当于,把1 向左移动15位。

二进制为:

1000 0000 0000 0000 -- 1 << 15

最终结果:

0000 0000 0000 0000 0000 0000 0001 1101 -- 29 0000 0000 0000 0000 1000 0000 0000 0000 -- 1 << 15 进行或运算 0000 0000 0000 0000 1000 0000 0001 1101 -- 相当于把29的第一位,变成了1,其他都没变。

所以,最终结果是,

曹工说JDK源码(3)--ConcurrentHashMap,Hash算法优化、位运算揭秘

这个数,换算为10进制,为32972,是个正数。

这个数,有啥用呢?

在addCount函数中,当整个哈希表的键值对数量,超过sizeCtl时(一般为0.75 * 数组长度),就会触发扩容。

java.util.concurrent.ConcurrentHashMap#addCount int sc = sizeCtl; boolean bSumExteedSizeControl = newBaseCount >= (long) sc; // 1 if (bContinue) { int rs = resizeStamp(n); // 2 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)) transfer(tab, nt); } // 3 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); newBaseCount = sumCount(); } else { break; }

1处,如果扩容条件满足

2处,如果sc小于0,这个sc是啥,就是前面说的sizeCtl,此时应该是等于:0.75 * 数组长度,不可能为负数

3处,将sc(此时为正数),cas修改为:

(rs << RESIZE_STAMP_SHIFT) + 2)

这个数有点意思了,rs就是前面我们的resizeStamp得到的结果。

按照前面的demo,我们拿到的结果为:

0000 0000 0000 0000 1000 0000 0001 1101 -- 相当于把29的第一位,变成了1,其他都没变。

因为

private static int RESIZE_STAMP_BITS = 16; private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxspg.html