6.ConcurrentHashMap jdk1.7

6.1 hash算法

就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
为了使得hash出来的值更加均衡,在concurrentHashMap里面使用了Wang/Jenkins hash算法再做了一次hash。

6.2 源码

6.2.1 构造方法

//DEFAULT_INITIAL_CAPACITY:默认的初始化容量,16
//DEFAULT_LOAD_FACTOR:默认的负载因子,0。75
//DEFAULT_CONCURRENCY_LEVEL:默认的并发等级,16
public ConcurrentHashMap() {
	this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
//点击this进去
 public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
		//参数校验 
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        //设置ssize的值,ssize是segments数组的大小,这里取的是大于等于concurrencyLevel的2^n的一个值。
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        //设置segmentShift和segmentMask的值
        //sshift就是上面描述中的n值,默认情况下concurrencyLevel等于16,sshift就等于4
        //因此默认情况下segmentShift的值就是28,这个值会参与hash运算。
        //segmentMask是hash运算的掩码,默认情况下等于16-1=15,类似于网络中的子网掩码,segmentMask的二进制最后几位都是1,最大值是末尾16个1(65535)。
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
		//初始化segment,其中cap是segment里面的HashEntry数组的长度。它取的是大于等于c(Map容量/ssize)的2^N的一个值
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        //创建segments和segments[0](这里面只初始化了一个segments数组中的第0个元素)。    
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0);
        this.segments = ss;
    }
	小结:
	--初始化currentHashMap时,初始化map的容量,负载因子,并发等级等信息
	--默认会在map里面新建一个segment数组,并且会只初始化segments数组中的第0个元素。

6.2.2 get方法

get方法 get操作是先定位到segment,然后再到segment中去获取对应的value值

public V get(Object key) {
    Segment<K,V> s;
    HashEntry<K,V>[] tab;
    int h = hash(key);
    // 根据Segment的索引((h >>> segmentShift) & segmentMask)算出在Segment[]上的偏移量
    // 默认情况下segmentShift为28,segmentMask为15(低位有1111),从而可以得到h的高四位的值。作为segment数组的索引。
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        //若segment存在则继续查找segment上面的table[]的索引位置
        //根据table的索引((tab.length - 1) & h)算出在table[]上的偏移量,循环链表找出结果
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}
执行流程小结:
1。定位segment的位置,通过segment的索引(h >>> segmentShift) & segmentMask)算出Segment[]上的偏移量。
2。 根据table的索引((tab.length - 1) & h)算出table[]上的偏移量。
3。 循环HashEntry链表直到找到结果。
4。因为没有加锁如果在get的时候,同时有线程修改了hashEntry的值可能会出现获取不到真实的值。出现弱一致性的问题。

6.2.3 put方法

对于put()操作,前面的定位Segment的操作都是和put()相同的。找到Segment以后,然后对整个Segment加锁,然后再进行后续的操作

1。整体思路:
concurrentHashMap里面包含了一个segement数组,而每个segement数组元素中维护了一个HashEntry链表。
a 根据key算出的hash值,确定是属于哪个segement
b 如果对应的脚标segement存在,
c 如果对应的脚标未存在,新建一个,通过cas操作设置到segement数组中去。
2。put方法
public V put(K key, V value) {
    Segment<K,V> s;
    //1.校验
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    // 2. 定位Segment,并判断其是否存在
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);//如果Segment不存在,则新建。
    return s.put(key, hash, value, false);// 如果Segment存在,提交给Segment去处理。调用Segment.put方法
}
  
//3 ensureSegment 分析 
@SuppressWarnings("unchecked")
private Segment<K,V> ensureSegment(int k) {
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // segment数组的偏移量
    Segment<K,V> seg;
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
        Segment<K,V> proto = ss[0]; // 使用 segment 0 作为原型,可以省略一些参数的计算
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);//创建Segment。使用cas创建直到成功
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}
//4 Segment.put()方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
	//尝试加锁。如果尝试失败
    HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        // 循环定位链表中的HashEntry位置,然后执行变更
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}


//小结

posted on 2019-01-08 09:14  lukelin1989  阅读(89)  评论(0编辑  收藏  举报

导航