设为首页 加入收藏

TOP

ConcurrentHashMap源码,看我这篇就够了(二)
2023-07-25 21:23:38 】 浏览:48
Tags:ConcurrentHashMap 源码
.75=1.5,插入第二个值时才会进行扩容。
  1. 计算segment数组容量的大小。
  2. 计算entrySet数组的大小。
  3. 初始化segment数组,其中生成一个s0对象放在数组的第0个位置
  4. 为什么首先需要一个s0存储到数组的第一个位置?

因为初始化数组完成后数组元素都还是null值,以后每一次添加一个元素的话,需要封装为entrySet对象,还需要对entrySet数组的大小重新计算,如果把第一次的计算结果全部存储到S0中,那么以后的话只需要直接拿来使用即可,不需要重新计算。虽然Segment对象不同,但是对象中属性内容其实是一样的。

  1. Segment数组的长度第一次已经确定,以后不会在改变,扩容是局部扩容,只对setrySet数组的容量进行扩容。

3. put

接着上面的初始化参数继续查看 put 方法源码。

/**
 * Maps the specified key to the specified value in this table.
 * Neither the key nor the value can be null.
 *
 * <p> The value can be retrieved by calling the <tt>get</tt> method
 * with a key that is equal to the original key.
 *
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 * @return the previous value associated with <tt>key</tt>, or
 *         <tt>null</tt> if there was no mapping for <tt>key</tt>
 * @throws NullPointerException if the specified key or value is null
 */
public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    // hash 值无符号右移 28位(初始化时获得),然后与 segmentMask=15 做与运算
    // 其实也就是把高4位与segmentMask(1111)做与运算
  
  // this.segmentMask = ssize - 1;
   //对hash值进行右移segmentShift位,计算元素对应segment中数组下表的位置
   //把hash右移segmentShift,相当于只要hash值的高32-segmentShift位,右移的目的是保留了hash值的高位。然后和segmentMask与操作计算元素在segment数组中的下表
    int j = (hash >>> segmentShift) & segmentMask;
   //使用unsafe对象获取数组中第j个位置的值,后面加上的是偏移量
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        // 如果查找到的 Segment 为空,初始化
        s = ensureSegment(j);
   //插入segment对象
    return s.put(key, hash, value, false);
}

/**
 * Returns the segment for the given index, creating it and
 * recording in segment table (via CAS) if not already present.
 *
 * @param k the index
 * @return the segment
 */
@SuppressWarnings("unchecked")
private Segment<K,V> ensureSegment(int k) {
    final Segment<K,V>[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment<K,V> seg;
    // 判断 u 位置的 Segment 是否为null
    if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
        Segment<K,V> proto = ss[0]; // use segment 0 as prototype
        // 获取0号 segment 里的 HashEntry<K,V> 初始化长度
        int cap = proto.table.length;
        // 获取0号 segment 里的 hash 表里的扩容负载因子,所有的 segment 的 loadFactor 是相同的
        float lf = proto.loadFactor;
        // 计算扩容阀值
        int threshold = (int)(cap * lf);
        // 创建一个 cap 容量的 HashEntry 数组
        HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck
            // 再次检查 u 位置的 Segment 是否为null,因为这时可能有其他线程进行了操作
            Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
            // 自旋检查 u 位置的 Segment 是否为null
            while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                   == null) {
                // 使用CAS 赋值,只会成功一次
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}

上面的源码分析了 ConcurrentHashMap 在 put 一个数据时的处理流程,下面梳理下具体流程。

  1. 计算要 put 的 key 的位置,获取指定位置的 Segment。

  2. 如果指定位置的 Segment 为空,则初始化这个 Segment.

    初始化 Segment 流程:

    1. 检查计算得到的位置的 Segment 是否为null.
    2. 为 null 继续初始化,使用 Segment[0] 的容量和负载因子创建一个 HashEntry 数组。
    3. 再次检查计算得到的指定位置的 Segment 是否为null.
    4. 使用创建的 HashEntry 数组初始化这个 Segment.
    5. 自旋判断计算得到的指定位置的 Segment 是否为null,使用 CAS 在这个位置赋值为
首页 上一页 1 2 3 4 5 下一页 尾页 2/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇SpringMVC(三):SpringMVC的两.. 下一篇实验三 Java类的继承与派生

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目