1.7中的ConcurrentHashMap原码分析

目录

一、简单介绍

二、方法分析

1、构造方法

2、put方法

3、rehash方法(扩容)

4、get方法

5、remove方法

三、总结


一、简单介绍

        ConcurrentHashMap是通过数组和链表来实现的,但是和HashMap不一样的是,其中有两个数组,一个是Segment<K,V>数组,用于存放Segment对象,一个是HashEntry<K,V>数组。

        基本的存储结构就像上图这样,Segment数组存放Segment对象。在其对象下面存放的是hashentry数组,而这个hashentry数组相当于一个hashmap一样的存储结构,数组加链表。Segment数组的默认长度是为16,hashentry数组默认长度等于,初始化长度/并发级别=1,这两个数据的默认值都是16,即在每个segment对象下面都含有一个hashentry数组大小为1(默认情况下)。

二、方法分析

主要从构造方法、pu和扩容这两个方法来分析

1、构造方法

①全参构造器

        传入的是三个参数initialCapacity:初始化容量、loadFactor:负载因子、concurrencyLevel:并发级别三个参数,先会进行参数的检验,防止负载因子和初始化容量小于0和并发级别小于等于0,成立即抛出异常。然后判断并发级别于最大值大小;经过while循环去计算需要初始化的Segment数组的大小,于并发级别相比,则并发级别是与Segment的大小是关联上的,ssize <<= 1,左移一位扩大两倍,为2的幂次方数。

        何为并发级别呢?即在多线程环境下,可以同时有多少个线程同时进行操作同一个ConcurrentHashMap对象。

        segmentShift这个参数后面需要用,是移位操作。segmentMask在计算传入的key所要放入的Segment对象在数组中的位置所需要的,即计算索引的时候。然后判断初始化容量于最大值的大小;c * ssize < initialCapacity判断这个,是为了向上取整,防止溢出,即为最小的hashentry容量;cap即为我说的hashentry数组的容量大小,默认为2,判断cap和c的大小,满足就左移扩大两倍,直到不满足为止,数组大小为2的幂次方数;然后定义一个Segment()对象S0,并初始化Segment数组大小为ssize;使用UNSAFE中的方法,原子性,有序写入S0,防止多线程下的不安全性。

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
    int sshift = 0;
    int ssize = 1;
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    this.segmentShift = 32 - sshift;
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    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];
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
    this.segments = ss;
    }

②其他构造器

        都会去调用全参构造器,只是把参数改变了而已,传入的参数替换默认的参数。

  public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
    }

    /**
     * Creates a new, empty map with the specified initial capacity,
     * and with default load factor (0.75) and concurrencyLevel (16).
     *
     * @param initialCapacity the initial capacity. The implementation
     * performs internal sizing to accommodate this many elements.
     * @throws IllegalArgumentException if the initial capacity of
     * elements is negative.
     */
    public ConcurrentHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }

    /**
     * Creates a new, empty map with a default initial capacity (16),
     * load factor (0.75) and concurrencyLevel (16).
     */
    public ConcurrentHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }

2、put方法

        四个参数,出了最后一个,其他三个应该都知道什么意思,最后一个onlyIfAbsent是标记,即在如果传入的key在其中是存在的,那么这个标记是为了判断是否替换其中的value值。

        第一步是拿锁tryLock(),因为Segment类不类继承了ReentrantLock所以可以使用其中的锁,我们先看拿到锁的语句,获取到table数组,计算传入的key在hashentry中的位置,(tab.length - 1) & hash和hashmap中计算位置是一样的计算方法,调用entryAt()拿到头节点,然后遍历这个数组下的链表;先判断头节点是否为null,不为null,在进行判断传入的key和该节点的key比较是否相等,oldValue记录该节点的value值,然后通过onlyIfAbsent来判断是否需要覆盖value值,需要覆盖记录一次modCount++,modCount为操作次数;如果该节点是没有值的时候,执行下面语句,然后判断node是否为null,满足即使用头插法,把传入的值改为新的头节点,其next节点为原来的frist节点;为null就new一个node作为头节点,把frist作为next节点,c = count + 1记录对象的个数。然后判断c与阈值比较,还要比较数组长度是否小于最大值,同事满足就进行扩容操作,不满足就直接插入即可,oldValue设置为null,然后break,最后释放锁,返回oldValue。

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);
                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;
        }

        在来看没有拿到锁的情况,同样先获取得到头节点,进行while循环tryLock()拿不到锁进循环,判断retries是否小于0,然后判断e是否为null,即头节点,满足在进行判断node是否为null,满足就会new一个node,把传入的值设置进去,将retries = 0,然后判断是否在这个链表中是否存在与传入的key相同的key,有也会 retries = 0;判断retries是否大于MAX_SCAN_RETRIES,成立就会调用lock()进行阻塞在这里。然后下面判断(retries & 1) == 0即为偶数成立,f = entryForHash(this, hash)) != first判断头节点是否发生改变,改变了就会再一次进入遍历链表。最后是返回node,

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) {
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }

        为什么要进行这些操作呢?①为了进行预热,提前准备好需要的操作;②使得循环变慢;③必须让其得到锁

3、rehash方法(扩容)

        扩容操作其实和hashmap的扩容有相同之处,先获得hashentry数组及其长度,新的数组长度的原有长度左移一位扩大两倍,阈值的新的数组长度乘以负载因子,new一个新的hashentry数组,for循环原来的数组,判断e是否为null,不为null就获取下一个节点,计算e在新的数组中的位置e.hash & sizeMask,然后判断next是否为null,为null那么把这个值e移到新数组中;不为next不为null,记录idx和e,然后在此for循环,计算e.next在新的数组中的位置,并且判断是否与上一个节点在新的数组中的位置是否相同,不相同就在此记录位置,其实这个for循环就是找到最后在新的数组中位置相同的对象,以方便把位置相同的node直接移动到新的数组中,下面的for循环其实就是将没有移动的node进行复制,然后进入下一此的外层for循环。外层循环结束后,执行最后的语句,计算传入的key值在新的数组中的位置,并把传入的node插入新的数组中的链表中。

private void rehash(HashEntry<K,V> node) {
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1;
            threshold = (int)(newCapacity * loadFactor);
            HashEntry<K,V>[] newTable =
                (HashEntry<K,V>[]) new HashEntry[newCapacity];
            int sizeMask = newCapacity - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                HashEntry<K,V> e = oldTable[i];
                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;
                        int lastIdx = idx;
                        for (HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            int k = last.hash & sizeMask;
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        newTable[lastIdx] = lastRun;
                        // Clone remaining nodes
                        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);
                        }
                    }
                }
            }
            int nodeIndex = node.hash & sizeMask; // add the new node
            node.setNext(newTable[nodeIndex]);
            newTable[nodeIndex] = node;
            table = newTable;
        }

4、get方法

        Segment<K,V> s这个操作主要是为了减少开销,获取hashentry数组tab,计算key的hashcode值,u计算其在segment中的位置,找到u所在的segment的对象进行判断是否为null并给s,还要判断s下的hashentry数组是否为null,同时不为null是才执行for循环,判断传入的key和找到的key进行比较,相等就返回这个key的value值,没有相同的key值的话,即没有找到,就返回null.

    public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            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;
    }

5、remove方法

        删除方法,其实和put是一样的,只是少了new node,其他的都是一样的,put懂了,这个就会了,返回oldValue,

 final V remove(Object key, int hash, Object value) {
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> e = entryAt(tab, index);
                HashEntry<K,V> pred = null;
                while (e != null) {
                    K k;
                    HashEntry<K,V> next = e.next;
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        V v = e.value;
                        if (value == null || value == v || value.equals(v)) {
                            if (pred == null)
                                setEntryAt(tab, index, next);
                            else
                                pred.setNext(next);
                            ++modCount;
                            --count;
                            oldValue = v;
                        }
                        break;
                    }
                    pred = e;
                    e = next;
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

三、总结

        其实concurrenthashmap是在hashmap的基础上增加了一些锁的操作,当你hashmap懂了之后在看这个集合会更加轻松。可能我的理解不是很全面,而且页没有在代码中写注释,但是我写的其实就是代码的解析,读懂了就明白基本的方法功能了,也没有把所有方法给分析,我只是把主要的方法分析了,其实其他的方法可以自己去研究研究,会加深你对concurrenthashmap的理解,谢谢阅读!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值