ConcurrentHashMap源码要点分析(JDK1.7)

数据结构图:

 

基本属性:

     //用于分段
     //允许的最大段数;用于绑定构造函数参数。必须是小于1小于24的幂。
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
    
    //根据这个数来计算segment的个数,segment的个数是仅小于这个数且是2的几次方的一个数(ssize)
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
 
    //每个分段表的最小容量。一定要有2个,至少2个,以避免在懒惰的建筑后使用下一个使用。
    static final int MIN_SEGMENT_TABLE_CAPACITY = 2;    
 
    
    //用于hashEntry'
    //默认的用于计算Segment数组中的每一个segment的HashEntry[]的容量,但是并不是每一个segment的HashEntry[]的容量
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    //这个表的默认加载因子,在构造函数中没有指定的时候使用
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    // 用于计算Segment数组中的每一个segment的HashEntry[]的最大容量(2的30次方)
    static final int MAXIMUM_CAPACITY = 1 << 30;

     // segments数组,每个元素是hashEntry数组
    final Segment<K,V>[] segments;

默认构造方法:

     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;
        // 上面这段基本的校验忽略

        // Find power-of-two sizes best matching arguments
        // 通过(1)可知ssize是segments数组的长度
        int sshift = 0;
        int ssize = 1;
        /** 这里根据确定了ssize是大于等于concurrencyLevel与concurrencyLevel最接近的2的n次方,
		sshift是1左移的次数,实际就是2的n次方的n*/
       while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift; // 这里用32是因为hash是32位(hash是int类型,int是32位),32-sshift相当于ssize的高位的占位数
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 计算segments数组每个元素的HashEntry数组的长度(segments数组每个元素由HashEntry数组组成)
        int c = initialCapacity / ssize;
		// initialCapacity无法被ssize整除,即还有剩余的HashEntry没分配到segments中去,
		所以平均分配后每个segments元素里数组长度再加1
		if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY; // 默认segments数组每个元素的HashEntry数组的长度
		// 保证segments每个元素的HashEntry数组长度是2的n次方(大于等于c最接近c的2的n次方)
        while (cap < c)
            cap <<= 1;
        // create segments and 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]; // (1)
      // segments 的第一个元素
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

put方法:

     public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key); // hash算法计算key的hash值
        int j = (hash >>> segmentShift) & segmentMask; // 计算segments数组下标
        // 通过下标获取到segments对应的Segment<K,V>
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        // 往s里面的HashEntry数组添加元素
        return s.put(key, hash, value, false); 
    }

  segmentShift 和 segmentMask分别是什么?假设现在采用默认构造函数 Map map = new ConcurrentHashMap();通过分析默认构造函数代码可知,segmentShift是segments数组长度(2的n次方)的高位占位数目,segmentMask = segments长度-1(值11111*); int j =(hash >>> segmentShift) & segmentMask就是hash的高位与segmentMask计算,即segments数组下标j取值为hash的高位。

            if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);

 segments的每个元素都是先初始化然后再使用。(s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null这个判断加上CAS保证了在高并发下只有一个线程初始化segments[j]成功, ensureSegment(j)方法会反复用到UNSAFE.getObject这个方法来确认segments[j]是否初始化了。这里需要查看Unsafe类的API,至于偏移量为啥是(j << SSHIFT) + SBASE),熟悉API后理解有也不能难,没必要深究。

     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;
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {// 再次确认segments[k]没被初始化
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype 使用段ss[0]作为原型
// ss[0]赋值在上面“默认构造方法”代码块倒数第二行,以ss[0]为原型是因为其他元素跟它的基本属性是一样的
// 如length和loadFactor,用这些属性来建新的segments元素
            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)) // 再次确认segments[k]没被初始化
                == null) { // recheck
//创建Segment对象,即segments的元素
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))// 再次确认segments[k]没被初始化
                       == null) {
// CAS操作,这里意思是如果ss[k] == null ,就将s赋值给ss[k],如果内存中ss[k] != null,那么就继续上面的while循环,
//比如这里是从break跳出while的,说明ss[k]是由当前线程初始化的,否则说明ss[k]已被其他数组初始化了,返回已有对象即可
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

得到segments[j]后,往segments[j]添加元素:

 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;
                // key在tab的位置,这里是取hash低位
                int index = (tab.length - 1) & hash;
                // table[index]上的第一个元素(取第一个是可能存在链表)
                HashEntry<K,V> first = entryAt(tab, index);
                // 遍历first
                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;// 这里如果不为null,说明first是链表,继续for循环,直到e=链表最后一个元素
                    }
                     // 头为空或者first链尾
                    else {
                        // 设为表头
                        if (node != null)
                            node.setNext(first);
                        else // hash冲突,生成一个关联key的HashEntry
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node); // 扩容
                        else  // 关联key的HashEntry添加到表中
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock(); // 解锁
            }
            return oldValue;
        }

 scanAndLockForPut 方法(加锁,hash冲突返回null,否则返回关联key的HashEntry):

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            HashEntry<K,V> first = entryForHash(this, hash); // 取表头
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            int retries = -1; // negative while locating node
            while (!tryLock()) {
                HashEntry<K,V> f; // to recheck first below
                if (retries < 0) {
                    // 表头为空,直接生成新元素,后然再走while,走枷锁程序,
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    // hash冲突
                    else if (key.equals(e.key))
                        retries = 0;
                     // 没hash冲突
                    else
                        e = e.next;
                }
                // 加锁
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                // 加锁前检查表头是否改变,如果改变了就重新走一遍scanAndLockForPut方法
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }

扩容(扩容后元素新位置等于原位置或原位置+原数组长度):

        //  node扩容后添加到新的数组中
      private void rehash(HashEntry<K,V> node) {
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1; // 扩容为2倍
            threshold = (int)(newCapacity * loadFactor);
            HashEntry<K,V>[] newTable =
                (HashEntry<K,V>[]) new HashEntry[newCapacity]; // 创建新数组
            int sizeMask = newCapacity - 1; // 新的掩码111*,假设新数组长度是2的n次方,新的掩码占位数目就是是n,
            for (int i = 0; i < oldCapacity ; i++) { // 遍历老数组,将原数组位置 i 处的链表拆分到新数组位置 i 和 i+oldCap 两个位置  
                HashEntry<K,V> e = oldTable[i];// e 是链表的第一个元素
                if (e != null) {
                    HashEntry<K,V> next = e.next;
                    int idx = e.hash & sizeMask; 
                    if (next == null)   //  Single node on list
                        newTable[idx] = e; // 只有一个元素,位置不变
                    // 链表
                    else { // Reuse consecutive sequence at same slot
                        HashEntry<K,V> lastRun = e;  // e 是链表表头  
                        int lastIdx = idx; // idx 是当前链表的头结点 e 的新位置
                       /**下面这个 for 循环会找到一个 lastRun 节点,这个节点之后的
                        所有元素是将要放到一起的 */  
                        for (HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            /** 计算新数组链表的位置,hash & sizeMask要么位置不变,要么就是原                                        
 位置+原数组长度*/
                            int k = last.hash & sizeMask; 
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        // 将 lastRun 及其之后的所有节点组成的这个链表放到 lastIdx 这个位置
                        newTable[lastIdx] = lastRun;
                        // Clone remaining nodes                       
                        /**下面的操作是处理 lastRun 之前的节点(lastRun在原链表中是尾部连续的一块),这些节点可能分配在另一个链表中,也可能分配到上面的那个链表中,由k决定*/
                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                            V v = p.value;
                            int h = p.hash;
                            int k = h & sizeMask; 
                            // 头插法,顺序倒置
                            HashEntry<K,V> n = newTable[k];	
                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                        }
                    }
                }
            }
            // 将新来的 node 放到新数组中刚刚的 两个链表之一的头部(头插法),
            int nodeIndex = node.hash & sizeMask; // add the new node           
            node.setNext(newTable[nodeIndex]); 
            newTable[nodeIndex] = node;
            table = newTable;
        }

实现过程:先对数组的长度增加一倍,然后遍历原来的旧的table数组,把每一个数组元素也就是Node链表迁移到新的数组里面,最后迁移完毕之后,把新数组的引用直接替换旧的。此外这里这有一个小的细节优化,在迁移链表时用了两个for循环,第一个for的目的是为了,判断是否有迁移位置一样的元素并且位置还是相邻,根据HashMap的设计策略,首先table的大小必须是2的n次方,我们知道扩容后的每个链表的元素的位置,要么不变,要么是原table索引位置+原table的容量大小。由于采用头插法,元素位置倒置。

此处参考:https://www.cnblogs.com/lfs2640666960/p/9621461.html

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值