ConcurrentHashMap源码解析

出现原因

  • 1hashmap在进行put时,会尝试扩容,扩容时,会使用rehash()这个函数,然后在重新将以前的节点前移到新的表中,可能会出现循环列表,a->b,b->a while(a.next!=null) a=b;原因是线程并发修改的导致的。使得cpu一直在空转,浪费了资源。
  • 2为什么不是用hashtable,hashtable的同步方法之间是互斥的,意思是 这个hashtable对这个对象使用是互斥的。一个线程使用这个对象的put方法,别的线程无法使用这个对象,从而进入阻塞状态。这是悲观锁的表现,一旦使用互斥对象就加锁。hashtable的效率很低。

ConcurrentHashmap的构造

ConcurrentHashMap源码解析_第1张图片

segment参数详解

if (concurrencyLevel > MAX_SEGMENTS)
    concurrencyLevel = MAX_SEGMENTS;

// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
    ++sshift;
    ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
this.segments = Segment.newArray(ssize);
  • ssize segment数组的大小。ssize的取值只有2
    的n次幂,1,2,4,8,16。通过上面计算后segment的大小一定是大于concurrentlevel的最小2的n次幂。
  • sshift,需要多少位就能定位到segment的数组的位置。假如,currentlevel的大小为14,那么由上可知ssize=16,sshift=4。2的4次幂=16就能表示出数组的0-15索引。此时如果一个字符串或者整数通过hash函数得到一个32位的hash值。我们通过取最后sshift大小的位数作为储存在segment的索引的话就能定位到那个数组中。实际情况下是取头4位,所以计算出segmentShift=28,通过将数据向右移位28位,就能得到32位数据中的头4位值。然后计算出具体值就能找到是一个数组。
  • -segmentMask=ssize-1;如果ssize=2的4次幂,那么最后结果segment=00000000|0000000|00000000|00001111,一个被传入数据的hash是32位|01010101|11111111|00000000|10101111|,hash&segment最后的结果就是取得hash值的最后四位,这种就是掩码的作用,不管传入的值为多少,两者相与最后得到该值的后四位。

segment的初始化

if (initialCapacity > MAXIMUM_CAPACITY)
    initialCapacity = MAXIMUM_CAPACITY;
//hashEntry需要移位的位数,不足以超过容量就加一
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
    ++c;
int cap = 1;
//hashEntry的大小是大于c的2的n幂
最后的结果就是使得hashentry的值大小cap等于使得ssize*cap>iniialCapacity并且cap为2的n次幂。
while (cap < c)
    cap <<= 1;
for (int i = 0; i < this.segments.length; ++i)
    this.segments[i] = new Segment(cap, loadFactor)

代码中的变量cap就是segment里HashEntry数组的长度,它等于initialCapacity除以ssize的倍数c,如果c大于1,就会取大于等于c的2的N次方值,所以cap不是1,就是2的N次方。segment的容量threshold=(int)cap*loadFactor,默认情况下initialCapacity等于16,loadfactor等于0.75,通过运算cap等于1,threshold等于零。

定位的过程

1取得该对象的本身的hashcode。像Integer类型就是自己本身,String重写了hashcode方法。
2通过Segement自带的hash函数,计算出新的值hash
3通过移动SegmentShift位数得到计算和的高4位,然后通过与运算计算得到最后sShift位数。就能得到该值的数组的位置。
4定位hashEntry[]数组的位置,使用上面计算好的hash&(table.lenght-1)

private static int hash(int h) {
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }
    
final Segment segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
  }
  
  hash >>> segmentShift) & segmentMask//定位Segment所使用的hash算法
int index = hash & (tab.length - 1);// 定位HashEntry所使用的hash算法

CurrentHashMap的get操作

1不加锁,乐观锁,自旋锁。
2所有共享变量设置为volatile,使得能被别的所见
3volatile写先于读取。

CurrentHashMap的put操作

1对所用共享变量加锁
2扩容时只对一个某个段HashEntry[]扩容为原来2倍,而不是对整个容器扩容

CurrentHashMap的size()

  • 1最安全的方法,将所有segment的put,remove,clean()锁住,然后读取每段的size大小。
  • 2不采用以上的方式,锁住太多东西,效率低
  • 3通过不加锁计算,尝试计算出size大小,如果某个segment大小改变了,那么segment的modcount也会改变。从而使得
    知道之前累加的size大小发送改变,重新再尝试计算。

你可能感兴趣的:(java集合框架)